在这一部分,我们会写一个辅助函数来展示如何利用Generator函数完成异步操作。在这之前,我们先复习一下,把参数传递给g.next
会发生些什么。如果你还记得前面的内容——如果你传递一个参数给g.next
,该参数的值将会替换上次yield
生成的值:
function* myGenerator(n) {
const a = (yield 10) + n
yield a
}
const g = myGenerator(1)
g.next().value // --> 10
g.next(100).value // --> 101
我们将在辅助函数中充分利用这一特性,现在,我们先写一个返回promise
对象的异步函数:
const asynTask1 = () => new Promise((r, j) => setTimeout(() => r(1), 1000))
这个函数返回一个promise
对象,并在1秒钟以后resolve
一个值1。现在,我们创建一个Generator函数,并在它的内部调用我们的异步函数:
const asynTask1 = () => new Promise((r, j) => setTimeout(() => r(1), 1000))
function* main() {
const result = yield asynTask1()
}
const g = main()
console.log(g.next())
猜想一下,上面的代码会输出什么呢?让我们来分析一下大概的执行流程:
- 首先,我们调用了Generator函数
main
,并把返回的generator对象赋值给变量g
- 然后,调用了
next
方法,来获取一个yield
表达式的值,也就是asyncTask1()
的值,它返回了一个promise
对象。 - 最后,我们把结果打印了出来:
{value: Promise { <pending> }, done: false}
- 1秒之后,这段代码执行完毕
代码执行完毕以后,我们无法通过generator对象获取到resolve
的值。除非,我们再次调用g.next
方法,并且在“合适”的时间把resolve
的值作为参数传入进去,在这个例子中,也就是使用resolve
的值来替换yield asyncTask1
。下面,我们更新一下上面的代码:
const asynTask1 = () => new Promise((r, j) => setTimeout(() => r(1), 1000))
function* main() {
const result = yield asynTask1()
return result //<-- return the resolved value and mark the end.
}
const g = main()
const next = g.next()
console.log(next) // --> { value: Promise { <pending> }, done: false }
next.value.then(v => {
// Resolve promise.
const r = g.next(v) // passing the resolved value to next.
console.log(r) // -> { value: 1, done: true }
})
我们在Generator函数中只是简单地增加了return
语句,并返回了resolve
的值。重要的是,当我们resolve promise对象
的时候,我们把resolve
的值传递给了g.next
。
接下来,我们就可以编写辅助函数了,这个辅助函数会接受一个Generator函数,并且完成上面我们讨论过的内容。如果没有需要再生成的值,它将会返回resolve
的值。首先,我们这样定义辅助函数:
const helper = gen => {
const g = gen()
}
目前来看,没有什么特别的,我们把Generator函数作为参数传递到了辅助函数中,并在其内部调用Generator函数,将结果赋值给了变量g
。下一步,我们需要定义一个函数来为我们调用g.next
方法:
const helper = gen => {
const g = gen()
function callNext(resolved) {
const next = g.next(resolved) // replace the last yield with the resolved value
if (next.done) return next.value // return the resolved value if not more items
return next.value.then(callNext) // pass `callNext` back again.
}
}
callNext
函数只接受一个参数:promise
对象的resolve
值,在其内部我们使用resolve
值调用g.next
方法,如果异步操作完成,那么generator对象中就会包含resolve
值;如果没有完成,那么我们就把callNext
方法作为then
方法的参数进行递归调用。如此,我们会一直调用g.next
方法获取生成的值,直到生成完所有的值为止。现在,我们可以使用辅助函数了,我们只需简单的调用它,并把Generator函数传递给它:
helper(function* main() {
const a = yield asynTask1()
console.log(a)
})
运行上面的代码,发现没有打印任何结果,这是因为我们还缺少了一个动作。我们没有调用callNext
函数,为此,我们需要让它自调用:
const helper = gen => {
const g = gen()
;(function callNext(resolved) {
const next = g.next(resolved)
if (next.done) return next.value
return next.value.then(callNext)
})() // <-- self invoking
}
现在,我们的辅助函数基本完成了,我们再加上一些错误捕获代码:
const helper = gen => {
const g = gen()
;(function callNext(resolved) {
const next = g.next(resolved)
if (next.done) return next.value
return next.value.then(callNext).catch(err => g.throw(err)) // <-- throw error
})()
}
如果异步任务出现任何异常,generator对象将抛出一个错误,此时我们可以直接在Generator函数中使用try-catch
语句来处理错误。所有的代码整合起来如下所示:
code/generators/async-flow.js
const asynTask1 = () => new Promise(r => setTimeout(() => r(1), 1000))
const asynTask2 = () =>
new Promise((r, j) => setTimeout(() => j(new Error('e')), 500))
const helper = gen => {
const g = gen()
;(function callNext(resolved) {
const next = g.next(resolved)
if (next.done) return next.value
return next.value.then(callNext).catch(err => g.throw(err))
})()
}
helper(function* main() {
try {
const a = yield asynTask1()
const b = yield asynTask2()
console.log(a, b)
} catch (e) {
console.log('error happened', e)
}
})
如果你对此比较感兴趣,你可以看看co库,那里有更全面的实现。我们将在下一章讨论async-await
,它们是基于Generator和Promise的高层抽象,用来解决异步问题。