- Published on
【笔记】- async/await 详解
- Authors
- Name
- McDaddy(戣蓦)
Babel是如何实现async/await的
通过Babel官方的在线转义得到编译后的代码
async function t() {
const x = await getResult();
const y = await getResult2();
return x + y;
}
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
try {
var info = gen[key](arg);
var value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
// 如果generator完成, 直接完成
resolve(value);
} else {
// 否则,如果value是普通值,那么执行下个_next, 如果是个promise,可以通过then来自行判断进入next还是throw
Promise.resolve(value).then(_next, _throw);
}
}
// 接收一个generator函数fn
function _asyncToGenerator(fn) {
return function() {
var self = this,
args = arguments;
// return 一个Promise符合async函数的返回,可以后续then
return new Promise(function(resolve, reject) {
// 得到一个generator
var gen = fn.apply(self, args);
// 定义一个_next,标示类型
function _next(value) {
// 递归
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
}
function _throw(err) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
}
_next(undefined);
});
};
}
function t() {
return _t.apply(this, arguments);
}
function _t() {
_t = _asyncToGenerator(function*() {
const x = yield getResult();
const y = yield getResult2();
return x + y;
});
return _t.apply(this, arguments);
}
async/await的优势
同步方式处理异步
function getData() { getRes().then((res) => { console.log(res); }) } // vs const getData = async function() { const res = await getRes(); console.log(res); }
获取中间值更方便直观
const morePromise = () => { return promiseFun1().then((value1) => { return promiseFun2(value1).then((value2) => { return promiseFun3(value1, value2).then((res) => { console.log(res); }) }) }) } // vs const morePromise = async function() { const value1 = await promiseFun1(); const value2 = await promiseFun2(value1); const res = await promiseFun3(value1, valuw2); return res; }
可以配合Promise.all来实现并行异步
const a = async function() { const res = await Promise.all[getRes1(), getRes2()]; return res; }
Generator的Babel实现
总体思想
- 维护一个整体全局的context,这样不论这个generator运行在哪一步都由这个全局来记录,并且由此来提供这个generator最新的状态和值
- 提供一个next方法,当调用时将context传入gen$函数
- gen$这个函数, 实际就是将generator中的每一个yield中的内容放到一个switch的case中,每次执行一次next,将指针指向next的位置,并且返回当前yield计算的值。如果接下来gen继续调用next的话,那switch就会走进上次指定的next case同时再重置next
- 实际过程中如果yield后的是promise,还要经历更复杂的处理
// promise中有很多问题 内部还是采用回调的方式 ,如果逻辑过多还是可能会导致 回调地狱
// 我们希望写的代码更像同步一些 generator
// koa1.0 用的是generator koa2 => async + await
// generator 函数可以实现暂停的功能 -> redux-saga (dva)
// yield 表示的是产出 * generator函数 (迭代器函数)
function gen$(context) {
switch (context.prev = context.next) {
case 0:
context.next = 1;
return 1
case 1:
console.log('第二步');
context.next = 2;
return 2
case 2:
context.next = 3;
return 3
case 3:
context.stop();
return 100
}
}
let gen = function() {
const context = {
prev: 0, // 当前要运行的
next: 0, // 下一次要运行的
done: false, // 是否完成运行
stop() {
this.done = true; // 更改完成状态
}
}
return {
next() {
return {
value: gen$(context), // 将上下文传入
done: context.done
}
}
}
}
// function* gen() { // 根据指针向下执行 + switch-case来实现
// yield 1
// yield 2
// yield 3
// return 100;
// }
let it = gen();
console.log(it.next()); // {value:1,done:false}
console.log(it.next()); // {value:2,done:false}
console.log(it.next()); // {value:3,done:false}
console.log(it.next()); // {value:undefined,done:true}
async/await原理
async/await = generator + co
核心思想:
- 借助generator通过指针可以暂停代码的特性,在下面代码的第7行,将yield之后的内容包装成一个Promise,并且仅当then时才step下一步,这样的结果就是只有当这个promise被resolve了,generator的指针才会走到下一步去,这样就完美实现了异步代码的同步化
- 借助co一次性完成迭代器的特性,不需要代码手动去调迭代器的next方法,一次性走完一个async函数
什么是co? co是一个可以一次性将一个迭代器执行完返回的库
想象一下,把下面的代码的*改成async,yield改成await。这就是一个我们熟悉的async/await函数。 此时通过编译就得到了如下的代码块。
let fs = require('fs').promises; // async + await === ganertor + co
function co(it) { // 异步迭代采用函数的方式 语法
return new Promise((resolve, reject) => {
function step(data) {
let { value, done } = it.next(data);
if (!done) {
Promise.resolve(value).then((data)=>{
step(data)
},reject); // 失败就失败了
} else {
resolve(value); // 将最终的结果抛出去
}
}
step();
})
}
function* read() { // switch - case => babel编译后就是把一个函数分成多个case 采用指针的方式向下移动
let name = yield fs.readFile('name.txt', 'utf8'); // => 返回结果
let age = yield fs.readFile(name, 'utf8');
return age;
}
co(read()).then(data => {
console.log(data);
}).catch(err => {
console.log(err);
})