相信大家在开发都使用过async和await,本文章先通过实现generator的简易原理,让大家深入了解generator。再来实现async/await原理。
1.Generator实现
理解生成器与迭代器
什么是生成器?
◼ 生成器函数也是一个函数,但是和普通的函数有一些区别:
- 首先,生成器函数需要在function的后面加一个符号:*
- 其次,生成器函数可以通过yield关键字来控制函数的执行流程:
- 最后,生成器函数的返回值是一个Generator(生成器-生成器事实上是一种特殊的迭代器)
什么是迭代器?
- 迭代器对象都有一个next()方法,每次调用都返回一个结果对象。
- next()返回的对象有如下的要求
- done(boolean)
- 如果迭代器可以产生序列中的下一个值,则为 false。(这等价于没有指定 done 这个属性。)
- 如果迭代器已将序列迭代完毕,则为 true。这种情况下,value 是可选的,如果它依然存在,即为迭代结束之后的默认返回值。
- value
- 迭代器返回的任何 JavaScript 值。done 为 true 时可省略
- done(boolean)
// 生成器函数
function* generaor() {
yield "aaa";
yield "bbb";
yield "ccc";
}
// 生成器
const gen = generaor();
// =====测试代码=====
console.log(gen.next()); // { value: 'aaa', done: false }
console.log(gen.next()); // { value: 'bbb', done: false }
console.log(gen.next()); // { value: 'ccc', done: false }
console.log(gen.next()); // { value: undefined, done: true }
原理实现
复制上面生成器函数的代码到babel官网中,转化成如下代码,看看在ES5下是如何实现Generator的:
源码中很多地方都进行封装比如:regeneratorRuntime.wrap、generaor$、_context等,所以自己实现一个简易的Generator。
function generaor$(_context) {
while (1) {
switch ((_context.prev = _context.next)) {
case 0:
_context.next = 2;
return "aaa";
case 2:
_context.next = 4;
return "bbb";
case 4:
_context.next = 6;
return "ccc";
case 6: // 迭代结束进入'end'
case "end":
return _context.stop();
}
}
}
function generaor() {
const ctx = {
prev: 0,
next: 0,
done: false,
stop() {
// 迭代完毕执行
this.done = true;
},
};
// 返回生成器
return {
next() {
return {
value: ctx.done ? undefined : generaor$(ctx), // 迭代结束返回undefine,没结束就进行判断
done: ctx.done,
};
},
};
}
// ======测试代码========
const gen = generaor();
console.log(gen.next()); // { value: 'aaa', done: false }
console.log(gen.next()); // { value: 'bbb', done: false }
console.log(gen.next()); // { value: 'ccc', done: false }
console.log(gen.next()); // { value: undefined, done: true }
2.async/await实现
那么我们要如何实现一个async/await呢,首先我们要知道,async/await实际上是对Generator(生成器)的封装,是一个语法糖。
下面我们通过generator函数模拟async
const axios = require("axios");
function* getUserRole() {
const res1 = yield axios.get("http://localhost:8000/user"); // 获取用户id
const id = res1.data.user.id;
const res2 = yield axios.get(`http://localhost:8000/user/role/${id}`); // 获取用户role
return res2.data.role.name;
}
const iterator = getUserRole(); // 迭代器
const { value,done } = iterator.next();
value.then((res) => { // 等pormise拿到结果再执行next(下同)
const { value,done } = iterator.next(res);
value.then((res) => {
const { value,done } = iterator.next(res);
console.log(res); // admin
});
});
从上面代码中可以看出,promise层层嵌套导致代码可读性极差,我们可以通过使用co函数(TJ大神写的能够使generator自动执行的函数库)对其进行异步迭代。
// 异步迭代函数(co函数)
function co(iterator) {
// 最终结果肯定返回一个promise
return new Promise((resolve, reject) => {
// 迭代器递归函数
function walk(data) {
const { value, done } = iterator.next(data); // 执行next
if (!done) { // 如果迭代没结束,继续递归迭代
Promise.resolve(value).then((res) => {
walk(res);
}, reject);
} else { // 迭代结束直接返回
resolve(value);
}
}
walk();
});
function* getUserRole() {
const res1 = yield axios.get("http://localhost:8000/user"); // 获取用户id
const id = res1.data.user.id;
const res2 = yield axios.get(`http://localhost:8000/user/role/${id}`); // 获取用户role
return res2.data.role.name;
}
// 将生成器传入异步迭代函数中,可直接自动执行,最后返回promise结果
co(getUserRole()).then((res) => {
console.log(res); // admin
});
}
以上就是async的核心原理,主要是在于co的封装,让generator可以自动执行,直到执行结束返回promise。
总结
由于Generator在开发中不经常使用,出现不久就被async/await取代了,所以很多同学对Generator比较陌生。其实实现generator的方法有很多,本文只通过babel编辑方式实现。
其实大家最重要还是要知道async是generator和promise的语法糖就可以了
generator + yield + co异步迭代函数 => async + await