前言
async/await是使用同步写法实现异步执行的语法糖。语法糖就是用简单的写法表达复杂的逻辑,所以要能够知道为什么这么写,就要搞明白它背后代表的复杂逻辑是什么。
本文参考了这篇大神的文章,是我自己学习之后按照自己的理解整理的一篇学习笔记。
# 7张图,20分钟就能搞定的async/await原理!为什么要拖那么久?
同步执行、异步执行和同步写法
首先,做一下概念辨析。
同步执行:代码是串行执行的,程序是阻塞的。
异步执行:代码是非串行执行的,程序是非阻塞的。
同步写法:代码是串行执行的,不考虑程序是否阻塞。
一个效果,两个特性
async/await
实现的效果是同步写法实现异步执行
,对应的具体要求有两点:
- 代码是串行执行的
- 程序是非阻塞的
为了实现这个效果,async/await有两个特性:
- async函数返回一个Promise
- await后面得是一个Promise,如果不是会自动转成Promise
// 模拟接口请求
function fn(num) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(num * 2);
}, 1000);
});
}
// 使用async/await构建业务逻辑
async function async() {
const num1 = await fn(1);
const num2 = await fn(num1);
const num3 = await fn(num2);
return num3;
}
在上述案例中,fn是异步代码,如果串行执行只能等待,但实际上程序又是非阻塞的,那么唯一的等待方式就是跳出async
函数,先执行外面的逻辑,等待fn
函数执行完了再回来继续执行。
生成器的秘密
能够跳出再回来的函数,就是生成器函数。
async/await语法糖的底层就是生成器。
生成器有两个特性:1. yield
跳出执行 2. next
继续执行
把上面的案例中的await
全部换成yield
就可以得到一个生成器函数:
// 使用生成器构建业务逻辑
function* gen() {
const num1 = yield fn(1);
const num2 = yield fn(num1);
const num3 = yield fn(num2);
return num3;
}
下面我们就用生成器实现async/await
的效果:1. 代码是串行执行的 2. 程序是非阻塞的。
const g = gen(); // 构建生成器
let res = null;
res = g.next(); // next()激活生成器,从头开始执行,执行到yield fn(1)跳出,返回fn(1)的结果
res = g.next(res.value); // next(res.value)激活生成器,从const num1 = res.value 开始执行,执行到yield fn(num1)跳出,返回fn(num1)的结果
res = g.next(res.value); // next(res.value)激活生成器,从const num2 = res.value 开始执行,执行到yield fn(num2)跳出,返回fn(num2)的结果
res = g.next(res.value); // next(res.value)激活生成器,从const num3 = res.value 开始执行,执行到return num3结束,返回num3
这里展示了生成器的串行执行过程,接下来我们把异步放进去,实现非阻塞执行。
这里fn()会返回一个Promise,所以只需要在Promise.then()
中执行g.next()
就可以了。
const g = gen(); // 构建生成器
let res = null;
res = g.next(); // next()激活生成器,从头开始执行,执行到yield fn(1),返回fn(1)的结果
res.value.then((data) => (res = g.next(data))); // 等待fn(1)返回的Promise执行完成后再next(data)激活生成器,本质就是await Promise()的过程
在Promise.then
中激活生成器,就是await Promise()
的过程,就是async/await
的第二个特性:
await后面得是一个Promise,如果不是会自动转成Promise
这里是单个Promise的情况,如果要实现多个Promise依序等待和激活,就要用到递归了。
const g = gen(); // 构建生成器
let res = null;
function go(data) {
res = g.next(data);
if (!res.done) {
res.value.then((data) => go(data)); // 这里是await Promise()的过程
} else {
return res.value;
}
}
go() // 由于内部是异步的,这里会返回undefined
这里的go()
函数就是驱动整个生成器异步执行的async
函数。不过由于内部是异步执行的,所以go()
执行完会直接返回undefined,拿不到最后的结果,因而有必要封装一个Promise获取返回值。
const g = gen();
let res = null;
function wrap() {
return new Promise((resolve) => { // 这里是async函数返回Promise的过程
function go(data) {
res = g.next(data);
if (res && !res.done) {
res.value.then((data) => go(data)); // 这里是await Promise()的过程
} else {
resolve(res.value); // 通过这一步把return num3的结果送出来
}
}
go();
});
}
wrap().then((data) => {
console.log(data);
});
wrap()
返回的Promise就是async/await
的第一个特性:
async函数返回一个Promise
所有我们就能够理解async/await
的两个特性不是偶然的,而是基于生成器实现同步写法实现异步执行
目标的必须。
用生成器实现async/await的完整代码
上一部分已经理解了基于生成器实现async/await
的基本原理,接下来就完善一下最终的代码:
// 返回一个生成器驱动函数
function generatorToAsync(genFn) {
// 驱动函数的主体
return function () {
const gen = genFn.apply(this, arguments); // gen有可能传参
// async会返回一个Promise
return new Promise((resolve, reject) => {
// 构建递归函数
function go(key, arg) {
// 执行生成器
let res;
try {
res = gen[key](arg);
} catch (error) {
// 生成器执行出错了
return reject(error);
}
const { value, done } = res;
// 执行完,退出(边界条件)
if (done) return resolve(value);
// 未执行完,继续递归
// Promise.resolve(value)用于实现await解析后面Promise的值
return Promise.resolve(value).then(
(val) => go('next', val),
// await后面Promise执行出错了
(err) => go('throw', err)
);
}
go('next');
});
};
}
const asyncFn = generatorToAsync(gen);
asyncFn().then((res) => console.log(res));
尾声
技术这东西就是不能糊里糊涂,每次梳理都有新的感悟。