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
本身是个函数,- 其返回值是一个对象,
value
是相同索引的yield
后面的内容 或 生成器函数的返回值,done
是标志函数是否执行完。 - 但是其参数却是,上一个索引
yield
的替换,也就是输入。换言之,yield
可以换成大一个索引的next参数
- 其返回值是一个对象,
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")
})
}
}
复制代码
引用
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END