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

今天群里有个小伙伴问了个问题

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

为什么 saga 不能用 async await 来实现呢?

想必开始接触 redux-saga 的同学都有这个疑问,为啥为要用 generator 的写法,用 async await 行不行。

import { put, call } from 'redux-saga/effects'
import { loginService } from '../service/request'

functionlogin(action{
    try {
        const loginInfo = yield call(loginService, action.account)
        yield put({ type'loginSuccess', loginInfo })
    } catch (error) {
        yield put({ type'loginFail', error })
    }
}

这个问题我刚开始用 saga 的时候也想问,但后来了解了 saga 的原理就想明白了。

下面我们就来探究一下。

saga 原理

我们从组件把 action 发给 store,这个过程是同步的。

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

但是有一些异步的过程加在哪里呢?中间件。

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

redux saga 会先把 action 直接透传给 store,这个过程是同步的。

然后再传递一份给 watcher saga,看一下是否是被监听的 action,如果是交给 worker saga 来处理,worker saga 处理的过程中可以 put 新的 action 到 store,这个过程是异步的。

这就是 redux-saga 的原理,原理上还是比较简单的,亮点在于异步过程的组织上,也就是 generator 上。

为什么说用 generator 来组织异步过程是 redux-saga 的亮点呢?

别急,我们先了解下什么是 generator。

generator

生成器(generator)是一个产生迭代器(iterator)的函数,通过 yield 返回一个个值。

而迭代器是一个有 value 和 done 属性的对象,用于顺序遍历某个集合。

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

我们可以调用 iterator.next 取到 yield 生成的一个个值。

也可以用 Array.from 或者展开运算符来取,这是 iterator 的特点。

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

除了 next 方法,迭代器还有 return 和 throw 方法,就像函数里的 return 和 throw 语句一样。

比如用 iterator.return 中止后续流程

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

用 iterator.throw 抛出错误

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

也就是说 generator 的执行是要由一个执行器来控制的,什么时候取下一个 yield 出的值,什么时候 next,什么时候 return 什么时候 throw 都是由执行器控制。

执行器可以通过 next、return、throw 的参数传递给 generator 执行后的结果:

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

上面这段代码就是一个小型 saga 了,原理就这么简单。

那为什么 async await 不行呢?

async await

当 generator 返回的值都是 Promise,那么执行 Promise 以后,只有 resolve 和 reject 两种结果,这个执行器就很固定,那自然可以写一个通用的执行器来自动调用 next、throw 和 return。

这个就是 async await 的原理,只不过被做成了语法糖。

async await 本质上不过是一个 generator 的执行器。

如果 redux-saga 用 async await 实现,那么所有的异步逻辑都要命令式的写在 await 后面,这样会导致异步过程很难测试。所以 redux-saga 自己实现了一个执行器。

再来看这段 saga 的代码:

import { put, call } from 'redux-saga/effects'
import { loginService } from '../service/request'

functionlogin(action{
    try {
        const loginInfo = yield call(loginService, action.account)
        yield put({ type'loginSuccess', loginInfo })
    } catch (error) {
        yield put({ type'loginFail', error })
    }
}

generator 中 yield 出的不是 promise,而是一个个 effect,这个其实就是一个对象,声明式的告诉 saga 执行器要做什么,而不是命令式的自己实现。

这样 generator 的执行器就根据不同的 effect 做不同的实现:

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

call effect 有对应的实现、put effect 也有对应的实现,也就是说 saga 的 generator 执行器不像 async await 那样什么都不做,而是有自己的 runtime 在的。

这样有什么好处呢?

  • 可以替换 saga effect 具体的执行逻辑,易于测试。比如从请求数据换成直接返回值,连 mock 都不用了。

  • 可以内置一系列的 saga,方便组织异步过程,比如 throttle、debounce、race 等

现在,我们回到最开始那个问题,redux-saga 能用 async await 实现么?

能,但是 async await 是一个 generator 的自动执行器,没有 runtime 逻辑,要命令式的把异步过程写在 saga 里。如果自己实现一个 generator 执行器,那么就可以把异步过程抽离出来,方便组织、方便测试。用 async await 实现 saga 的话,那就失去了灵魂。

总结

redux-saga 的原理是透传 action 到 store,然后再传一份 aciton 到 saga 组织的异步过程,saga 分为 watcher saga 和 worker saga。watcher saga 判断 action 是否要处理,然后交给 wroker saga 处理。

生成器 generator 是返回迭代器 iterator 的函数,iterator 有 next、throw、return 等方法,需要配合一个执行器来执行。

async await 本质上就是一个 generator 的自动执行器。

用 async await 实现 redux saga 的话,那就要开发者命令式的组织异步过程,还难以测试。

所以 redux-saga 自己实现了一个 generator 执行器,自带 runtime。generator 只要返回 effect 对象来声明式的说明要执行什么逻辑,然后由相应的 effect 实现来执行。

这种声明式的思路除了易于组织异步过程外,还有非常好的可测试性。

generator + saga 执行器的设计是 redux-saga 的灵魂,所以不能用 async await 来实现。


原文始发于微信公众号(神光的编程秘籍):为什么 redux-saga 不能用 async await 实现

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由半码博客整理,本文链接:https://www.bmabk.com/index.php/post/110618.html

(0)
神光的编程秘籍的头像神光的编程秘籍

相关推荐

发表回复

登录后才能评论
半码博客——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!