為什么 Redux-Saga 不能用 Async Await 實現(xiàn)
今天群里有個小伙伴問了個問題
為什么 saga 不能用 async await 來實現(xiàn)呢?
想必開始接觸 redux-saga 的同學(xué)都有這個疑問,為啥為要用 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 發(fā)給 store,這個過程是同步的。
但是有一些異步的過程加在哪里呢?中間件。
redux saga 會先把 action 直接透傳給 store,這個過程是同步的。
然后再傳遞一份給 watcher saga,看一下是否是被監(jiān)聽的 action,如果是交給 worker saga 來處理,worker saga 處理的過程中可以 put 新的 action 到 store,這個過程是異步的。
這就是 redux-saga 的原理,原理上還是比較簡單的,亮點在于異步過程的組織上,也就是 generator 上。
為什么說用 generator 來組織異步過程是 redux-saga 的亮點呢?
別急,我們先了解下什么是 generator。
generator
生成器(generator)是一個產(chǎn)生迭代器(iterator)的函數(shù),通過 yield 返回一個個值。
而迭代器是一個有 value 和 done 屬性的對象,用于順序遍歷某個集合。
我們可以調(diào)用 iterator.next 取到 yield 生成的一個個值。
也可以用 Array.from 或者展開運(yùn)算符來取,這是 iterator 的特點。
除了 next 方法,迭代器還有 return 和 throw 方法,就像函數(shù)里的 return 和 throw 語句一樣。
比如用 iterator.return 中止后續(xù)流程
用 iterator.throw 拋出錯誤
也就是說 generator 的執(zhí)行是要由一個執(zhí)行器來控制的,什么時候取下一個 yield 出的值,什么時候 next,什么時候 return 什么時候 throw 都是由執(zhí)行器控制。
執(zhí)行器可以通過 next、return、throw 的參數(shù)傳遞給 generator 執(zhí)行后的結(jié)果:
上面這段代碼就是一個小型 saga 了,原理就這么簡單。
那為什么 async await 不行呢?
async await
當(dāng) generator 返回的值都是 Promise,那么執(zhí)行 Promise 以后,只有 resolve 和 reject 兩種結(jié)果,這個執(zhí)行器就很固定,那自然可以寫一個通用的執(zhí)行器來自動調(diào)用 next、throw 和 return。
這個就是 async await 的原理,只不過被做成了語法糖。
async await 本質(zhì)上不過是一個 generator 的執(zhí)行器。
如果 redux-saga 用 async await 實現(xiàn),那么所有的異步邏輯都要命令式的寫在 await 后面,這樣會導(dǎo)致異步過程很難測試。所以 redux-saga 自己實現(xiàn)了一個執(zhí)行器。
再來看這段 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 執(zhí)行器要做什么,而不是命令式的自己實現(xiàn)。
這樣 generator 的執(zhí)行器就根據(jù)不同的 effect 做不同的實現(xiàn):
call effect 有對應(yīng)的實現(xiàn)、put effect 也有對應(yīng)的實現(xiàn),也就是說 saga 的 generator 執(zhí)行器不像 async await 那樣什么都不做,而是有自己的 runtime 在的。
這樣有什么好處呢?
- 可以替換 saga effect 具體的執(zhí)行邏輯,易于測試。比如從請求數(shù)據(jù)換成直接返回值,連 mock 都不用了。
- 可以內(nèi)置一系列的 saga,方便組織異步過程,比如 throttle、debounce、race 等
現(xiàn)在,我們回到最開始那個問題,redux-saga 能用 async await 實現(xiàn)么?
能,但是 async await 是一個 generator 的自動執(zhí)行器,沒有 runtime 邏輯,要命令式的把異步過程寫在 saga 里。如果自己實現(xiàn)一個 generator 執(zhí)行器,那么就可以把異步過程抽離出來,方便組織、方便測試。用 async await 實現(xiàn) saga 的話,那就失去了靈魂。
總結(jié)
redux-saga 的原理是透傳 action 到 store,然后再傳一份 aciton 到 saga 組織的異步過程,saga 分為 watcher saga 和 worker saga。watcher saga 判斷 action 是否要處理,然后交給 wroker saga 處理。
生成器 generator 是返回迭代器 iterator 的函數(shù),iterator 有 next、throw、return 等方法,需要配合一個執(zhí)行器來執(zhí)行。
async await 本質(zhì)上就是一個 generator 的自動執(zhí)行器。
用 async await 實現(xiàn) redux saga 的話,那就要開發(fā)者命令式的組織異步過程,還難以測試。
所以 redux-saga 自己實現(xiàn)了一個 generator 執(zhí)行器,自帶 runtime。generator 只要返回 effect 對象來聲明式的說明要執(zhí)行什么邏輯,然后由相應(yīng)的 effect 實現(xiàn)來執(zhí)行。
這種聲明式的思路除了易于組織異步過程外,還有非常好的可測試性。
generator + saga 執(zhí)行器的設(shè)計是 redux-saga 的靈魂,所以不能用 async await 來實現(xiàn)。