最近看了一篇神光大佬的文章:为什么 redux-saga 不能用 async await 实现 ,看完之后深有体会。虽然这篇文章没有讲具体实现原理,但是总结了两个浅显易懂的道理:
async/await
本质上是一个generator
的自动执行器,没有 runtime 逻辑;saga
实际上也是实现了generator
执行器,但是有自己的 runtime ,可以将异步过程抽离出来,便于测试;
这篇文章就通过 async/await
的实现原理,来分析 redux-saga
的原理。
1. async/await 实现原理
前面提到,async/await
是 generator
的执行器,想要实现 async/await
就要搞清楚 generator
是怎么回事。
1) 什么是 generator 函数
generator
是一个产生迭代器的函数,而 yield
表达式是暂停执行的标记,必须调用迭代器对象的 next
方法,恢复函数的执行,使得指针移向下一状态。
function *gen() {
yield 1;
yield 2;
return "ending";
}
let g = gen();
g.next(); // {value: 1, done: false}
g.next(); // {value: 2, done: false}
g.next(); // {value: 'ending', done: true}
g.next(); // {value: undefined, done: true}
在上面的代码中,首先调用 gen
函数返回一个迭代器,然后调用了四次 next
方法。
第一次调用,generator
函数开始执行,直到遇到第一个 yield
表达式为止,然后对 yield
表达式后的表达式进行求值,作为 value
属性的值返回,done
属性的值为 false
,表示遍历还没有结束。
第二次调用,generator
函数将 next
方法的入参(如果有)作为 yield
表达式的返回值,然后从上次 yield
表达式停下的地方,一直执行到下一个 yield
表达式,然后对 yield
表达式后的表达式进行求值,作为 value
属性的值返回,done
属性的值为 false
,表示遍历还没有结束。
第三次调用,generator
函数将 next
方法的入参(如果有)作为 yield
表达式的返回值,然后从上次 yield
表达式停下的地方,一直执行到 return
语句(如果没有 return
语句,就执行到函数结束),然后将 return
语句后面的表达式的值,作为 value
属性的值返回,done
属性的值为 true
,表示遍历已经结束。
第四次调用,此时 generator
函数已经运行完毕,next
方法返回对象的 value
属性的值为 undefined
,done
属性的值为 true
。以后再调用 next
方法,返回的都是这个值。
需要注意:
yield
表达式后的的表达式,只会在next
方法将指针移到这一句时,才会求值;yield
表达式本身没有返回值,next
方法可以带一个参数,该参数会作为上一个yield
表达式的返回值;
generator
函数实际上和 iterator
接口非常相似,可以使用 for...of
遍历,也可以使用扩展运算符展开到数组中。相比 iterator
接口,generator
除了有 next
方法之外,还有 return
方法用于终止后续流程,以及 throw
方法用于抛出错误。
2) 最简单的执行器
知道了上面这些,我们就可以来实现一个 generator
的执行器。例如有下面一个 generator
函数:
const fullfilled = (param) => {
return new Promise(resolve => setTimeout(resolve, 1000, param))
}
function *effect() {
const a = yield fullfilled("===测试1===");
console.log(a);
const b = yield fullfilled("===测试2===");
console.log(b);
}
先来看看手动执行应该怎么做:
const gen = effect();
// 开始执行 generator
gen.next().value.then(res => {
// 将第一个 Promise 的结果作为 yield 表达式的返回值
// 打印 "===测试1==="
gen.next(res).value.then(res => {
// 将第二个 Promise 的结果作为 yield 表达式的返回值
// 打印 "===测试2==="
gen.next(res);
})
})
有了这个思路之后,实现执行器就很容易了:
function asyncToGenerator(fn) {
const gen = fn();
let step = (val) => {
const { value, done } = gen.next(val);
// 如果迭代没有结束,就通过 then 方法拿到上一步的结果
// 然后递归调用 step 进行下一次迭代
!done && value.then(step);
}
step();
}
3) 返回 Promise
上面的代码仅仅只是实现了执行器,跟 async/await
还是不一样的。我们知道 async
函数会将函数内部 return
的值,包裹在 Promise
中返回,我们就来实现这个功能:
function asyncToGenerator(fn) {
// 返回一个 Promise 实例
return new Promise((resolve, reject) => {
const gen = fn();
let step = (val) => {
const { value, done } = gen.next(val);
if (done) {
// 如果迭代结束,直接 resolve 最后的结果
// 如果函数没有返回值就是 undefined
resolve(value);
} else {
// 如果迭代没有结束,就通过 then 方法拿到上一步的结果
// 如果 Promise 成功,就递归调用 step 进行下一次迭代
// 如果 Promise 失败,则直接 reject ,不往下执行
value.then(step, reject);
}
}
step();
})
}
4) 支持非 Promise
类型
正常情况下,await
命令右边是一个 Promise
对象。如果不是 Promise
对象,就直接返回对应的值:
function asyncToGenerator(fn) {
return new Promise((resolve, reject) => {
const gen = fn();
let step = (val) => {
const { value, done } = gen.next(val);
if (done) {
resolve(value);
} else {
// 这边为了省事,不进行判断
// 直接对非 Promise 类型包裹一个 Promise
Promise.resolve(value).then(step, reject);
}
}
step();
})
}