為什么 Redux-Saga 不能用 Async Await 實現
今天群里有個小伙伴問了個問題
為什么 saga 不能用 async await 來實現呢?
想必開始接觸 redux-saga 的同學都有這個疑問,為啥為要用 generator 的寫法,用 async await 行不行。
- import { put, call } from 'redux-saga/effects'
- import { loginService } from '../service/request'
- function* login(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 會先把 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 屬性的對象,用于順序遍歷某個集合。
我們可以調用 iterator.next 取到 yield 生成的一個個值。
也可以用 Array.from 或者展開運算符來取,這是 iterator 的特點。
除了 next 方法,迭代器還有 return 和 throw 方法,就像函數里的 return 和 throw 語句一樣。
比如用 iterator.return 中止后續流程
用 iterator.throw 拋出錯誤
也就是說 generator 的執行是要由一個執行器來控制的,什么時候取下一個 yield 出的值,什么時候 next,什么時候 return 什么時候 throw 都是由執行器控制。
執行器可以通過 next、return、throw 的參數傳遞給 generator 執行后的結果:
上面這段代碼就是一個小型 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'
- function* login(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 做不同的實現:
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 來實現。








































