从一道题目开始:
function* gen() {const a = yield 1console.log(a)}
为了让其能成功打印出 1, 设计如下函数:
function step(gen) {const it = gen()let resultreturn function() {result = it.next(result).value}}
进行如下调用:
var a = step(gen)a()a() // 1
从这个题目总结出规律:
next
的调用数比 yield
的调用数多 1;next
传参无效, 从第二个 next
开始传参有效并会作为 yield
的结果返回;生成器中的 yield/next
除了控制能力外还有双向的消息通知能力:
yield
后面跟的值能通过 it.next().value
取到it.next()
括号中的值又能作为 yield
的结果返回function* foo(url) {try {const value = yield request(url)console.log(value)} catch (err) {...}}const it = foo('http://some.url.1')
yield
后面跟着的语句执行完再进入暂停状态的, 在如上代码中, 当执行 it.next()
时, 可以稍加转换为如下形式:
function* foo(url) {try {const promise = request(url) // 当执行 it.next() 时, 这里是被执行的const value = yield promise // 这里被暂停console.log(value)} catch (err) {...}}
return
function* gen() {yield 1return 2console.log('是否执行')}const it = gen()it.next() // {value: 1, done: false}it.next() // {value: 2, done: true}it.next() // {value: undefined, done: true}
总结: 遇到 return
, generator
函数结束中断, done
变为 true
;
iterator
的 throw
function* gen() {yield 1console.log('是否执行')}var it = gen()it.throw(new Error('boom')) // Error: boomit.next() // {value: undefined, done: true}
总结: 遇到 iterator
的 throw
, generator
函数运行中断, done
变为 true
;
Generator
是一个返回迭代器的函数, 下面是其简版实现:
function foo(url) {var statevar valfunction process(v) {switch (state) {case 1:console.log('requesting:', url)return request(url)case 2:val = vconsole.log(val)returncase 3:var err = valconsole.log('Oops:', err)return false}}return {next: function(v) {if (!state) {state = 1return {done: false,value: process()}} else if (state === 1) {state = 2return {done: true,value: process(v)}} else {return {done: true,value: undefined}}},throw: function() {if (state === 1) {state = 3return {done: true,value: process(e)}} else {throw e}}}}var it = foo('http://some.url.1')
以 co
库来说, 现在已经统一为 Generator + Promise
的调用方式, 下面进行简单的模拟:
co(function* () {const result = yield Promise.resolve(true)console.log(result) // true})
// 简版 promisefunction co(gen) {const it = gen()const step = function(data) {const result = it.next(data)if (result.done) {return result.value}result.value.then((data) => {step(data)})}step()}
观察 co
库发现, co
函数后返回的是 promise
, 使用如下:
co(function* () {const result = yield Promise.resolve(true)return result // 这里有个语法, it.next() 碰到 return 后, 其值会变为 { value: result, done: true } 的形式}).then((data) => {console.log(data) // true})
我们再对其稍加改造, 使之更加添近 co
库:
function co(gen) {return new Promise((resolve, reject) => {const it = gen()let resultconst step = function(fn) {try {result = fn()} catch(e) {return reject(e)}if (result.done) { return resolve(result.value) }result.value.then((data) => {step(() => it.next(data))}, (err) => {step(() => it.throw(err)) // 这里为了让抛错直接在 generator 消化, 所以 step 内改传函数})}step(() => it.next())})}