Promise、Generator和Async

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 对象, 所以它们可以被链式调用。

https://mdn.mozillademos.org/files/8633/promises.png这就是一个 Promise 的整个执行状态,引用至 MDN

实现一个 Promise

看起来 Promise 的原理还算简单,那下面动手实现一个简单的 Promise。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
const pending = "pending";
const resloved = "resloved";
const rejected = "rejected";

class MyPromise {
constructor(callback) {
this._status = pending;
if (typeof callback !== "function") {
throw new TypeError("callback is not a function");
}
callback(this.reslove.bind(this), this.reject.bind(this));
}

err = null;

data = null;

errHandler = null;

successHandler = null;

reslove(data) {
this.data = data;
this._status = resloved;
this.successHandler();
}

reject(err) {
this.err = err;
this._status = rejected;
this.errHandler();
}

then(successHandler) {
this.successHandler = successHandler;
return this;
}

catch(errHandler) {
this.errHandler = errHandler;
}
}

当然实际运用中还需要考虑多种情况,这里不做过多说明。

Generator

基本概念

Generator 函数是 ES6 提供的一种异步编程解决方案,Generator 函数有多种理解角度。语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。

执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。

Generator 函数最直观的就是 function 关键字后紧跟 *,而且函数内部可以使用 yield 关键字。Generator 函数调用之后并不会马上执行,而是返回了一个遍历器对象(Iterator Object)。如:

1
2
3
4
5
6
7
8
9
10
11
function* say() {
yield "嘻嘻";
yield "哈哈";
return "呵呵";
}

const Say = say();
Say.next(); // {value: "嘻嘻", done: false}
Say.next(); // {value: "哈哈", done: false}
Say.next(); // {value: "呵呵", done: true}
Say.next(); // {value: undefined, done: true}

通过上面的例子可以看到, Generator 函数需要使用遍历器对象的 next 方法进行调用,然后返回一个一个 value 为 yield 值的对象,当迭代完成时,返回对象的 done 响应的变成 true。

yield 表达式本身没有返回值,next 也是可以接受参数的,当 next 传入参数时,则将该参数作为上一个 yield 表达式当返回值。

由于 Generator 函数返回了一个遍历器对象(Iterator Object),所以也可以使用 for…of、…、Array.from 等遍历器接口方法在内部调用,如:

1
2
3
4
5
6
7
8
function* say() {
yield "嘻嘻";
yield "哈哈";
return "呵呵";
}
[...say()]; // ['嘻嘻','哈哈','呵呵']
Array.form(say()); // ['嘻嘻','哈哈','呵呵']
const [x, y, z] = say(); // x: '嘻嘻',y: '哈哈',z: '呵呵'

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function* say() {
yield "嘻嘻";
yield "哈哈";
return "呵呵";
}
function* justSay() {
yield "小明";
yield* say();
yield "哼哼";
return "语无伦次";
}
const min = justSay();
min.next(); // {value: "小明", done: false}
min.next(); // {value: "嘻嘻", done: false}
min.next(); // {value: "哈哈", done: false}
min.next(); // {value: "哼哼", done: false}
min.next(); // {value: "语无伦次", done: false}

结果出乎意料的是 “呵呵” 丢失了,需要注意的是如果 Generator 中有 return 语句,就需要使用赋值语句获取最后的返回值,如:

1
2
3
4
...
const sayd = yield* say()
console.log(sayd) // 呵呵
...

使用 Generator 实现异步编程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function* asyncTask(params) {
try {
const val1 = yield http1(params);
const val2 = yield http2(val1);
const val3 = yield http3(val2);
const val4 = yield http4(val3);
} catch (e) {
// err handler
}
}

const task = asyncTask();
task.next();
task.next();
task.next();
task.next();
// ...

注意,上面这种做法,只适合同步操作,即所有的 task 都必须是同步的,不能有异步操作。因为这里的代码一得到返回值,就继续往下执行,没有判断异步操作何时完成。

async

ES2017 标准引入了 async 函数,使得异步操作变得更加方便。

async 函数是什么?一句话,它就是 Generator 函数的语法糖。

实现原理

async 的实现原理就是将 Generator 函数和自动执行器,包装在一个函数里。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
async function fun(args) {
// ...
}

function fun(args) {
return spawn(function* () {
// ...
});
}

// 所有的 async 函数都可以写成第二种形式,下面是 spawn 函数的实现
function spawn(genF) {
return new Promise(function (reslove, reject) {
const gen = genF();
function step(nextF) {
let next;
try {
next = nextF();
} catch (e) {
return reject(e);
}
if (next.done) {
return resolve(next.value);
}
Promise.resolve(next.value).then(
function (val) {
step(function () {
return gen.next(val);
});
},
function (e) {
step(function () {
return gen.throw(e);
});
}
);
step(function () {
return gen.next(undefined);
});
}
});
}

实例

由于现在已经使用很广泛了,所以就不上例子了。

比较

阮老师的教材我就不抄了,详情请移步:async 函数

Promise: 虽然 Promise 的写法比回调函数的写法大大改进,但是一眼看上去,代码完全都是 Promise 的 API(then、catch 等等),操作本身的语义反而不容易看出来。

Generator: 语义比 Promise 写法更清晰,用户定义的操作全部都出现在 spawn 函数的内部。这个写法的问题在于,必须有一个任务运行器,自动执行 Generator 函数,上面代码的 spawn 函数就是自动执行器,它返回一个 Promise 对象,而且必须保证 yield 语句后面的表达式,必须返回一个 Promise。

Async: 可以看到 Async 函数的实现最简洁,最符合语义,几乎没有语义不相关的代码。它将 Generator 写法中的自动执行器,改在语言层面提供,不暴露给用户,因此代码量最少。如果使用 Generator 写法,自动执行器需要用户自己提供。

[越努力,越幸运!]