Promise、Generator 和 Async 都是 JavaScript 的异步解决方案,这里简单的做一个对比。
Promise
基本概念
在上一节里我们简单的介绍了 Promise,一个 Promise 对象只有以下三种状态:
- pending: 初始状态,既不是成功,也不是失败状态。
- fulfilled: 意味着操作成功完成。
- rejected: 意味着操作失败。
一个 pending 状态的 Promise 对象可能会变成 fulfilled 状态或者 rejected,一旦 Promise 当状态发生改变就会调用 Promise 的 then 或者 catch 方法。
因为 Promise.prototype.then 和 Promise.prototype.catch 方法返回 promise 对象, 所以它们可以被链式调用。
这就是一个 Promise 的整个执行状态,引用至 MDN
实现一个 Promise
看起来 Promise 的原理还算简单,那下面动手实现一个简单的 Promise。
1 | const pending = "pending"; |
当然实际运用中还需要考虑多种情况,这里不做过多说明。
Generator
基本概念
Generator 函数是 ES6 提供的一种异步编程解决方案,Generator 函数有多种理解角度。语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。
执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。
Generator 函数最直观的就是 function 关键字后紧跟 *,而且函数内部可以使用 yield 关键字。Generator 函数调用之后并不会马上执行,而是返回了一个遍历器对象(Iterator Object)。如:
1 | function* say() { |
通过上面的例子可以看到, Generator 函数需要使用遍历器对象的 next 方法进行调用,然后返回一个一个 value 为 yield 值的对象,当迭代完成时,返回对象的 done 响应的变成 true。
yield 表达式本身没有返回值,next 也是可以接受参数的,当 next 传入参数时,则将该参数作为上一个 yield 表达式当返回值。
由于 Generator 函数返回了一个遍历器对象(Iterator Object),所以也可以使用 for…of、…、Array.from 等遍历器接口方法在内部调用,如:
1 | function* say() { |
next、throw、和 return
Generator 函数的原型上有 next、throw、和 return 等方法,这三个方法本质上是同一件事,它们都是让 Generator 函数恢复执行,并使用不同等语句替换 yield 表达式。
next:将 yield 表达式替换成一个值
throw:将 yield 表达式替换成一个 throw 语句(throw 方法抛出的错误要被内部捕获,前提是必须至少执行过一次 next 方法)
return:将 yield 表达式替换成一个 return 语句
yield* 表达式
如果在 Generator 函数内部,调用另一个 Generator 函数。需要在前者的函数体内部,自己手动完成遍历。ES6 提供了 yield*表达式,作为解决办法,用来在一个 Generator 函数里面执行另一个 Generator 函数。如:
1 | function* say() { |
结果出乎意料的是 “呵呵” 丢失了,需要注意的是如果 Generator 中有 return 语句,就需要使用赋值语句获取最后的返回值,如:
1 | ... |
使用 Generator 实现异步编程
1 | function* asyncTask(params) { |
注意,上面这种做法,只适合同步操作,即所有的 task 都必须是同步的,不能有异步操作。因为这里的代码一得到返回值,就继续往下执行,没有判断异步操作何时完成。
async
ES2017 标准引入了 async 函数,使得异步操作变得更加方便。
async 函数是什么?一句话,它就是 Generator 函数的语法糖。
实现原理
async 的实现原理就是将 Generator 函数和自动执行器,包装在一个函数里。
1 | async function fun(args) { |
实例
由于现在已经使用很广泛了,所以就不上例子了。
比较
阮老师的教材我就不抄了,详情请移步:async 函数
Promise: 虽然 Promise 的写法比回调函数的写法大大改进,但是一眼看上去,代码完全都是 Promise 的 API(then、catch 等等),操作本身的语义反而不容易看出来。
Generator: 语义比 Promise 写法更清晰,用户定义的操作全部都出现在 spawn 函数的内部。这个写法的问题在于,必须有一个任务运行器,自动执行 Generator 函数,上面代码的 spawn 函数就是自动执行器,它返回一个 Promise 对象,而且必须保证 yield 语句后面的表达式,必须返回一个 Promise。
Async: 可以看到 Async 函数的实现最简洁,最符合语义,几乎没有语义不相关的代码。它将 Generator 写法中的自动执行器,改在语言层面提供,不暴露给用户,因此代码量最少。如果使用 Generator 写法,自动执行器需要用户自己提供。
[越努力,越幸运!]