从 async/await 看 redux-saga 原理

最近看了一篇神光大佬的文章:为什么 redux-saga 不能用 async await 实现 ,看完之后深有体会。虽然这篇文章没有讲具体实现原理,但是总结了两个浅显易懂的道理:

  • async/await 本质上是一个 generator 的自动执行器,没有 runtime 逻辑;
  • saga 实际上也是实现了 generator 执行器,但是有自己的 runtime ,可以将异步过程抽离出来,便于测试;

这篇文章就通过 async/await 的实现原理,来分析 redux-saga 的原理。

1. async/await 实现原理

前面提到,async/awaitgenerator 的执行器,想要实现 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 属性的值为 undefineddone 属性的值为 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();
  })
}

2. redux-saga 的原理

参考

为什么 redux-saga 不能用 async await 实现

手写async await的最简实现(20行)

手写Redux-Saga源码

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值