async函数实现了异步代码以近乎同步的方式写,可读性强,使用的时候真是非常方便。
其本质是生成器函数的语法糖,本文尝试手写一个函数来替换async函数,去掉糖衣看看。
async函数的 demo
先来一个async函数的 demo:
const getData = () =>
new Promise((resolve) => setTimeout(() => resolve('data'), 1000));
async function test() {
const data = await getData();
console.log('data: ', data);
const data2 = await getData();
console.log('data2: ', data2);
return 'success';
}
// 这样的一个函数 应该再1秒后打印data 再过一秒打印data2 最后打印success
test().then((res) => console.log(res));
换成生成器函数的 demo
每个async函数,其实是在类似执行一个生成器函数,而转化成生成器函数也很简单:
- 去掉
async - 加上
* - 将
await换成yield
示例转化下:
const getData = () =>
new Promise((resolve) => setTimeout(() => resolve('data'), 1000));
function* testGen() {
const data = yield getData();
console.log('data: ', data);
const data2 = yield getData();
console.log('data2: ', data2);
return 'success';
}
生成器函数的基础知识
运行完整个生成器函数的话,
next的调用次数 =yield的出现次数 + 1
先看个简单的 demo:
function* gen() {
const data = yield '第 1 个yield';
console.log('data: ', data);
const data2 = yield '第 2 个yield';
console.log('data2: ', data2);
return 'success';
}
const it = gen();
var { value, done } = it.next('第 1 个next');
var { value, done } = it.next('第 2 个next');
var { value, done } = it.next('第 3 个next');
理解next和yield语句尤为关键
next让函数内部代码开始执行,next本身是个函数,- 其返回值是一个对象,第n个next函数的返回值(value属性) 是 第n个
yield后面的内容 或 生成器函数的返回值,done是标志函数是否执行完。 - 但是第n个
yield,却是替换成 第n+1个next的参数
- 其返回值是一个对象,第n个next函数的返回值(value属性) 是 第n个
next执行的时候,遇到yield就暂停
将生成器函数以伪代码的方式,表达下:
伪代码里将yield和next全部换掉,并注上暂停的点,这样我觉得就理解的七七八八了
function* gen() {
// const data = yield '第 1 个yield';
const data = '第 2 个next'; // 第 2 个 next的参数,这也是第一个暂停处,注意并没赋值
console.log('data: ', data);
// const data2 = yield '第 2 个yield';
const data2 = '第 3 个next'; // 第 3 个 next的参数,这也是第二个暂停处,注意并没赋值
console.log('data2: ', data2);
return '生成器函数的返回值'; // 生成器函数运行完毕
}
// it是迭代器
const it = gen();
// 第一个next函数的返回值是 第一个yield后面的内容
// var { value, done } = it.next('第 1 个next');
var value = '第 1 个yield',
done = false; // 第一个next函数的返回值是 第一个yield后面的内容
// var { value, done } = it.next('第 2 个next');
var value = '第 2 个yield',
done = false; // 第二个next函数的返回值是 第二个yield后面的内容
// var { value, done } = it.next('第 3 个next');
var value = '生成器函数的返回值',
done = true; // 注意,这里done为true,所以value就是是生成器函数的返回值
所以可见,遇到yield赋值语句的时候,一定提醒自己,跟yield后面的内容没有一毛钱关系!
遇到next的赋值语句的时候,value和next的参数没有一毛钱关系!
让生成器手动执行
再回到主体的生成器函数的例子:
const getData = () =>
new Promise((resolve) => setTimeout(() => resolve('data'), 1000));
function* testGen() {
const data = yield getData(); // data = 第2个next的参数
console.log('data: ', data);
const data2 = yield getData(); // data = 第3个next的参数
console.log('data2: ', data2);
return 'success';
}
想让data是数据的话,跟yield后面的并没关系,而是跟next的传入有关。
const it = testGen();
// value是 第1个yield的后面的getData() 其实就是promise实例,
let res = it.next();
let promise = res.value;
let done = res.done;
promise.then((data) => {
// promise是 第2个yield的后面的getData() 其实就是promise实例。注意这里的next参数才是上面data的赋值
res = it.next(data);
promise = res.value;
done = res.done;
promise.then((data) => {
// done为true,promise是 生成器函数的返回值。注意这里的next参数才是上面data2的赋值
res = it.next(data);
promise = res.value;
done = res.done;
});
});
让生成器函数自动执行
将上面的过程封装成一个函数,让其自动执行生成器函数,可以将重复的地方拿出来:
function co(gen, ...args) {
return (...args) => {
const it = gen(...args);
// 首次先运行
let res = it.next();
let promise = res.value;
let done = res.done;
// 重复的部分封装下:
const fn = () => {
if (done) {
return Promise.resolve(promise);
}
promise.then((data) => {
// done为true,promise是 生成器函数的返回值。注意这里的next参数才是上面data2的赋值
res = it.next(data);
promise = res.value;
done = res.done;
// 继续往前走
fn();
});
};
fn();
};
}
co(testGen)();
更好的版本
考虑到异常情况,其实更复杂一点。
这边直接搬运手写 async await 的最简实现(20 行)
function asyncToGenerator(generatorFunc) {
return function () {
const gen = generatorFunc.apply(this, arguments);
return new Promise((resolve, reject) => {
function step(key, arg) {
let generatorResult;
try {
generatorResult = gen[key](arg);
} catch (error) {
return reject(error);
}
const { value, done } = generatorResult;
if (done) {
return resolve(value);
} else {
return Promise.resolve(value).then(
(val) => step('next', val),
(err) => step('throw', err)
);
}
}
step('next');
});
};
}
思路:
function asyncToGenerator(generatorFunc) {
// 返回的是一个新的函数
return function() {
// 先调用generator函数 生成迭代器
// 对应 var gen = testG()
const gen = generatorFunc.apply(this, arguments)
// 返回一个promise 因为外部是用.then的方式 或者await的方式去使用这个函数的返回值的
// var test = asyncToGenerator(testG)
// test().then(res => console.log(res))
return new Promise((resolve, reject) => {
// 内部定义一个step函数 用来一步一步的跨过yield的阻碍
// key有next和throw两种取值,分别对应了gen的next和throw方法
// arg参数则是用来把promise resolve出来的值交给下一个yield
function step(key, arg) {
let generatorResult
// 这个方法需要包裹在try catch中
// 如果报错了 就把promise给reject掉 外部通过.catch可以获取到错误
try {
generatorResult = gen[key](arg)
} catch (error) {
return reject(error)
}
// gen.next() 得到的结果是一个 { value, done } 的结构
const { value, done } = generatorResult
if (done) {
// 如果已经完成了 就直接resolve这个promise
// 这个done是在最后一次调用next后才会为true
// 以本文的例子来说 此时的结果是 { done: true, value: 'success' }
// 这个value也就是generator函数最后的返回值
return resolve(value)
} else {
// 除了最后结束的时候外,每次调用gen.next()
// 其实是返回 { value: Promise, done: false } 的结构,
// 这里要注意的是Promise.resolve可以接受一个promise为参数
// 并且这个promise参数被resolve的时候,这个then才会被调用
return Promise.resolve(
// 这个value对应的是yield后面的promise
value
).then(
// value这个promise被resove的时候,就会执行next
// 并且只要done不是true的时候 就会递归的往下解开promise
// 对应gen.next().value.then(value => {
// gen.next(value).value.then(value2 => {
// gen.next()
//
// // 此时done为true了 整个promise被resolve了
// // 最外部的test().then(res => console.log(res))的then就开始执行了
// })
// })
function onResolve(val) {
step("next", val)
},
// 如果promise被reject了 就再次进入step函数
// 不同的是,这次的try catch中调用的是gen.throw(err)
// 那么自然就被catch到 然后把promise给reject掉啦
function onReject(err) {
step("throw", err)
},
)
}
}
step("next")
})
}
}