ES7 规范 async await 原理
一、什么是 async/await?
async/await 是 ES7 引入的新规范,称之为协程。对于携程官方有段描述大致如下:一个线程里可以有多个协程,但是同时只能执行一个协程。
二、async/await 和 generator/promise 是什么关系
async/await 是 generator/promise 实现机制的语法糖。也就是说 async/await 的本质就是 generator/promise 的组合而已,关于 promise 我就不再细说了,不大了解的童鞋可以自行前往官网。
三、手动实现一个 async/await
首先我们来看下,一个标准的 async/await 函数写法:
async function foo() {
const res = await 'generator';
console.log(res) // 返回 generator
}
foo()
其中 async 关键字表示的是该函数是个协程函数,并且该函数的返回值是 promise,而 await 关键字等同于 generaor 的 yield
那么 generator 函数长啥样呢?
function* foo() {
const res = yield 'generator';
console.log(res); // 返回一个对象 {done: false, value: 'generator'}
}
foo().next(); // generator 函数的返回 yield 值得方法
我们在把这段代码改装一下
function* foo() {
const res = yield Promise.resolve('generator');
console.log(res); // 返回一个对象 {done: false, value: Promise}
}
foo().next(); // generator 函数的返回 yield 值得方法
我们的 generator 函数返回的是一个 Promise 函数,而不像 async/await 函数一样直接返回结果字符串,当然我们可以这样:
function* foo() {
const res = yield Promise.resolve('generator');
res.then((str) => {
console.log(str); // 返回 generator
});
}
foo().next(); // generator 函数的返回 yield 值得方法
虽然也能拿到我们想要的结果,但是确很麻烦并且代码也不美观,如果这时候再多加一个 yield 返回 Promise 结果可想而知。那么 我们怎样才能像 async/await 直接返回想要的结果呢?我们接着来改装一下 foo 函数:
function* foo() {
const res = yield Promise.resolve('generator');
console.log(res); // 返回 generator
}
function co(gen) {
gen = gen();
result = gen.next();
return result.value.then((res) => {
return gen.next(res);
});
}
co(foo);
经过进一步改装之后,我们也能够像 async/await 在 foo 函数里直接拿到 generator 结果字符串,这就成功模拟实现了 async/await 语法糖。 接下来我们来一步步分析下这段代码:
function co(gen) {
gen = gen();
const result = gen.next(); // 返回一个对象 {done: false, value: Promise}
return result.value.then((res) => {
// 执行 Promise函数
return gen.next(res); // 将结果返回给 yield 变量,generator 函数的 next(param) 方法支持传递一个参数,如果传入参数则把参数值返回给 yield 前面声明的变量,如上 foo 中的 const res = yield Promise.resolve('generator'); ,res的就是可以用来接收 next(param) 中的 param 值
});
}
co 函数允许传入一个参数,很显然这个参数就是我们的 foo 协程函数,所以 可以直接声明一个 result 函数来接收返回值,至此就实现了 async/await 的基本功能,我们测试下:
function co(gen) {
gen = gen();
const result = gen.next(); // 返回一个对象 {done: false, value: Promise}
return result.value.then((res) => {
// 执行 Promise函数
return gen.next(res); // 将结果返回给 yield 变量,generator 函数的 next(param) 方法支持传递一个参数,如果传入参数则把参数值返回给 yield 前面声明的变量,如上 foo 中的 const res = yield Promise.resolve('generator'); ,res的就是可以用来接收 next(param) 中的 param 值
});
}
function* foo() {
const res = yield Promise.resolve(1);
console.log(res); // 返回1
}
// 执行co函数
co(foo);
我们可以看到 foo 函数就类似于 async/await 版的
async function foo() {
const res = await Promise.resolve(1);
console.log(res); // 返回1
}
我们会发现这边还是会有一个问题,那就是这里的例子只有一个 yield ,实际中我们会有多个 yield ,如下:
function co(gen) {
gen = gen();
const result = gen.next(); // 返回一个对象 {done: false, value: Promise}
return result.value.then((res) => {
// 执行 Promise函数
return gen.next(res); // 将结果返回给 yield 变量,generator 函数的 next(param) 方法支持传递一个参数,如果传入参数则把参数值返回给 yield 前面声明的变量,如上 foo 中的 const res = yield Promise.resolve('generator'); ,res的就是可以用来接收 next(param) 中的 param 值
});
}
function* foo() {
const res1 = yield Promise.resolve(1);
console.log(res1);
const res2 = yield Promise.resolve(2);
console.log(res2);
}
// 执行co函数
co(foo);
执行完上面代码之后,我们在控制台里发现,只打印出了 1,这是为什么呢?因为我们的 co 函数只执行了一次 result.then ,而我们的测试用例是 2 个 promise,如何解决呢?递归就行。我们改进一下 co 函数
function co(gen) {
gen = gen();
// 封装 dep 函数,做递归调用
function dep(data) {
const result = gen.next(data); // 返回一个对象 {done: false, value: Promise}
if (result.done) {
// 终结条件:没有 yield 退出,避免死循环
return;
}
return result.value.then((res) => {
// 执行 Promise函数
return dep(res); // 将结果返回给 yield 变量,generator 函数的 next(param) 方法支持传递一个参数,如果传入参数则把参数值返回给 yield 前面声明的变量,如上 foo 中的 const res = yield Promise.resolve('generator'); ,res的就是可以用来接收 next(param) 中的 param 值
});
}
dep();
}
function* foo() {
const res1 = yield Promise.resolve(1);
console.log(res1);
const res2 = yield Promise.resolve(2);
console.log(res2);
}
// 执行co函数
co(foo);
执行完改进的 co 函数后,我们发现能够在控制台上看到 1 和 2。至此,一个简化版的 co 函数就实现了,当然完整的 co 函数处理的细节还很多,但是基本的核心代码我们就算实现完成了,有不足之处,欢迎在评论中指出。