在该文中,我们希望封装一个异步函数的语法糖,使得它能够兼具回调函数、Promise、Generator 以及 async/await 的写法特点,形成一个通用的异步编程解决方案。目的是在异步编程的过程中,以不变的方法,应万变的方式。在本文中,前四小节主要介绍如何使用各种方式来进行异步函数 asyncMethod 的调用,了解了如何调用后,最后一节的着重点在于设计该异步函数 asyncMethod,使得它满足之前提到的所有调用场景。
回调函数
回调函数可以说是 JS 语言一个非常鲜明的特性,它允许我们在未来的某个时间执行一个函数。JS 语言本身就提供了非常多支持回调函数的 API,比如定时器函数:1
2
3setTimeout(function() {
// 执行逻辑
}, 1000);
在 Node.js 中,设计回调函数的一个通用原则是把错误和结果都放到回调函数中做处理,往往是把错误 err 作为第一个参数,结果 val 作为第二个参数。所以 asyncMethod 的调用方式设计如下:1
2
3
4
5
6
7asyncMethod(data, (err, val) => {
if (err) {
console.log(err);
} else {
console.log(val);
}
});
Promise
Promise 是异步编程的一种解决方案,它的出现完美优雅的解决了回调地狱的问题。Promise 的用法如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14const promise = new Promise(function(resolve, reject) {
// ... 执行逻辑
if (/* 异步操作成功 */){
resolve(val);
} else {
reject(err);
}
});
promise.then(function(value) {
// 成功
}, function(error) {
// 失败
});
有了对 Promise 基本用法的掌握后,我们希望 asyncMethod 的调用方式兼容 Promise:1
2
3
4
5
6
7asyncMethod(data)
.then(val => {
console.log(val);
})
.catch(err => {
console.log(err);
});
Generator 函数
Generator 是 ES6 提出的一种异步编程解决方案。它是一个状态机,封装了多个内部状态。Generator 函数的基本形式如下:1
2
3
4
5
6
7
8
9function *numberGenerator() {
yield 1;
yield 2;
return 3;
}
const ng = numberGenerator();
ng.next(); // { value: 1, done: false }
ng.next(); // { value: 2, done: false }
ng.next(); // { value: 3, done: true }
该函数包含三个状态:1,2 和 3。调用该函数时,返回一个遍历器对象,代表 Generator 函数的内部指针。当该遍历器调用 next 方法时,才会遍历下一个状态,所以其实是提供了一种可以暂停执行的函数,而这一点其实是将 Generator 函数用于异步场景的关键点。所以 Generator 函数的一个特点就是每遇到一个 yield,函数就将执行权交出,等到 yield 后面的操作执行完毕后,继续执行;若 yield 后面跟了一个异步操作,那么我们可以在这之后得到本次异步操作的结果,这种写法的优点之一就是可以变异步为“同步”,写起来会十分流畅。理解了 Generator 函数的异步理念后,可以设想 asyncMethod 的用法如下:1
2
3
4
5
6
7
8function *gen() {
try {
let val = yield asyncMethod(data);
console.log(val);
} catch (err) {
console.log(err);
}
}
但是 Generator 函数的执行是需要通过不断调用 next 函数来实现的,因而可以在上面函数的最外层再套一个 genWrapper 用来控制流程的执行,genWrapper 可以自行进行实现,当然最好使用 tj 大神写的 co 模块。具体代码如下:1
co(gen);
讲到这里呢,其实可以发现 asyncMethod 对待 Generator 函数并没有特殊之处,所以 Generator 的异步方式对 asyncMethod 的具体实现不产生影响。
async/await
async 函数是 Generator 函数的语法糖。
- 它的语法类似于 Generator 函数,把 * 换成 async,把 yield 换成 await
- Generator 函数的执行必须依靠执行器,async 函数通过封装自带执行器
- 返回 Promise 对象,可以使用 then 指定下一步的操作
async 函数在调用时与同步操作一样,只要一行就可以搞定。asyncMethod 的用法如下:1
2
3
4
5
6
7
8
9async function asyncWrapper(data) {
try {
let val = await asyncMethod(data);
console.log(val);
} catch (err) {
console.log(err);
}
}
asyncWrapper(data);
通用异步编程方案
终于进入正题,前面已经总结过使用回调函数,Promise,Generator 函数和 async 函数调用 asyncMethod 的方法,其实不难发现,真正有启发性的是回调函数和 Promise,因为另外两种用法只要返回 Promise 就可支持。asyncMethod 的实现如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25const asyncMethod = function () {
const args = Array.prototype.slice.call(arguments);
const data = args[0];
const cb = args[1];
const promise = new Promise((resolve, reject) => {
if (typeof data === 'number') {
resolve(data);
} else {
reject(data);
}
});
if (typeof cb === 'function') {
return promise
.then(val => {
cb.call(this, null, val);
})
.catch(err => {
cb.call(this, err);
});
}
return promise;
};
注明
data 是应该替换为真实的数据,我使用如下两个变量来进行的测试(你可以把 data 替换为 number 或者 error)
1
2const number = 1;
const error = new Error('error');以上的代码全部复制到编辑器中就可以运行(不要忘记安装 co 模块)
- 本文主要说明思想,很多场景可能无法覆盖,望见谅