今天一朋友去大厂面试完之后回来就跟我疯狂吐槽,说问得好深,感觉回答得不太好。
其中有一题就是问:你是如何理解async函数的。
大致面试过程如下:
朋友:async函数可以结合promise用于控制异步流程,以同步的思考方式来编写异步的代码,可以提高代码的可读性。
面试官:你这只是说明了使用的方法还有它的好处,还有其他的吗?它的原理是什么?
朋友:emmm.........
当时心里就在想,大厂不亏是大厂,连语法糖都不放过,不过想想也是,一般大厂如果问到这种简单的问题,回答一般都是要牵涉到原理的,那async/await的原理到底是什么?
原理
async函数的原理其实就是generator函数结合一个自动迭代器。
这里建议先去了解一下ES6中的generator
我们先来看一个简单的例子:
let it = null;
function* gen() {
let data = yield getData();
console.log(data);
}
function getData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Hello Generator');
}, 1000);
})
.then(
(data) => {
// 异步数据获取成功后,通过调用迭代器的next方法将data传入data
it.next(data);
},
...
);
}
// 创建迭代器
it = gen();
// 启动generator迭代器,执行到第一个yiled语句
it.next();
这里我们启动it.next(),generator执行到第一个yiled语句后暂停,这时候getData执行,数据异步获取后在内部通过调用it.next(data)将数据data传输到generator内部,这时generator继续执行内部的data就被赋值了'Hello Generator'并打印出来。
// 打印:Hello Generator
上面我们是通过了Promise内部去控制了Generator的执行,从而让我们能以一种同步的方式去控制异步的流程。这只是一个雏形,我们可不可以以async函数使用方式为蓝本用Generator和Promise去创建这样一种模式呢?答案是可以的,我们只需要加一个会自动迭代Generator的启动器就可以了。
首先我们用Generator模拟一下async函数,先创建两个异步操作p1和p2,用于在1s后异步获取值
let p1 = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 1000)
});
}
let p2 = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(2);
}, 1000)
});
}
接着我们写一个Generator函数:
function* gen() {
let x = yield p1();
console.log(`x: ${x}`);
let y = yield p2();
console.log(`y: ${y}`);
}
其实到这里已经非常像async函数了,下面是async版本
async gen() {
let x = await p1();
console.log(`x: ${x}`);
let y = await p2();
console.log(`y: ${y}`);
}
现在我们就只差一个会自动迭代Generator的启动器就可以完美的模拟async函数了。下面我们来编写一个简单的run函数
function run(gen) {
let it = gen();
return Promise.resolve(it.next())
.then(
function handleNext(next) {
// 结束执行
if (next.done) return;
// 传入Promise
return Promise.resolve(next.value)
.then(
(res) => {
// 将Promise的结果输入回生成器当中,并继续执行
handleNext(it.next(res));
}
)
}
)
}
// 将gen生成器函数传入,gen函数就会执行
run(gen);
// 打印:
// x: 1
// y: 2
run函数执行会先执行传入的生成器得到一个迭代器,返回的yield对象如果还能继续执行则将会等到当前Promise完成后自动进行下一个迭代。
原理大致就是这样了,继续撸码了。