[譯]在 Redux 中使用 AJAX 輪詢(二):Saga 篇

劉嘉一發表於2019-03-04

不久之前我寫了一篇關於在 React 中使用 AJAX 輪詢的短文,內容可以概括為如何發起和控制週期性 AJAX 請求。文中我證明了通過使用元件生命週期方法,原生 React 和 Redux 在技術上就足以解決 AJAX 輪詢的控制問題。隨著時間推移,在使用中我發現這個方法需要開發者非常細心地篩選和管理 componentWillReceiveProps 中傳入的 props 。最終,我的目標變成了儘可能地清除元件中的非同步邏輯。

在 Redux 生態中,已有不少管理副作用(side effect)的類庫,從最基礎的 redux-thunk,到受 Elm 薰陶的 redux-loop,最後還有使用 Generator 函式強力驅動的 redux-saga

理想情況下,我喜歡把所有的非同步請求都放置到一個 API 中介軟體中,這種用法可以參考 Redux 官方例項 real-world example。若使用 thunk 會使我的 Action 建立函式被非同步邏輯所汙染,所以 redux-thunk 已然出局。使用 redux-loop 則會與我的中介軟體相沖突,作為 store 的一個 enhancer 它卻修改了 store 的 signature,進而導致其下游的所有中介軟體都需要調整。所以綜上我決定探索 redux-saga,它本質上提供給我的是在應用後臺執行任務的能力。使用 redux-saga 可以保證我利用中介軟體集中控制非同步邏輯的用法不變,同時通過設定各類不同的觀察者(watcher)來觸發副作用。那麼如何使用 redux-sage 處理 AJAX 輪詢呢?

// 延時副作用的工具函式
function delay(millis) {  
    const promise = new Promise(resolve => {
        setTimeout(() => resolve(true), millis)
    });
    return promise;
}

// 每隔 20 秒獲取一次資料                                           
function* pollData() {  
    try {
        yield call(delay, 20000);
        yield put(dataFetch());
    } catch (error) {
        // 取消異常 -- 如果你願意也可以捕獲
        return;
    }
}

// 等待上一次資料請求返回成功後,發起下一輪輪詢
// 如果使用者登出,則取消本次未完成的輪詢                                          
function* watchPollData() {  
    while (true) {             
        yield take(DATA_FETCH_SUCCESS);
        yield race([
            call(pollData),
            take(USER_LOGOUT)
        ]);
    }
}

// 讓各類任務在後臺並行執行                       
export default function* root() {  
    yield [
        fork(watchPollData)
        // 此處可包含其他觀察者
    ];
}
複製程式碼

這種以 sagas 存在的輪詢邏輯讓開發者免於處理元件中潛在的複雜生命週期。我在 race 條件中新增了 USER_LOGOUT Action,這樣可以代勞之前 componentWillUnmountclearTimeout 的工作。當傳送 logout Action 後,執行中的 pollData saga 就可以被很好地中斷執行。

其餘涉及到的邏輯如下:

dataFetch — 它是一個 Action 建立函式,產生的 Action 會被 API 中介軟體攔截並處理。在中介軟體中會發起真正的 API 請求,並根據請求結果發出一系列後續 Action。

watchPollData — 它是一個隨應用啟動並一直執行的 saga。啟動後它會阻塞 saga 執行並監聽 DATA_FETCH_SUCCESS Action 的發出。一旦監聽到相應的 Action 被髮出,它就解除阻塞繼續執行後續的 pollData saga。

pollData — 先阻塞 Generator 函式的執行,20秒後再呼叫 dataFetch 並 dispatch dataFetch 產生的 Action。

此處用到的 takeputracecallfork 作用符,都可以在 redux-saga documentation 中找到。

你可以將本文的新方法與前一篇文章中在元件內做控制的方法作比較,使用 saga 後更利於預測和集中管理我的副作用。需要注意的是並不是所有的瀏覽器都支援 Generator 函式,如果你使用了 ES2015 和 Babel,那麼它們已經提供了 Generator 函式的瀏覽器 polyfill 相容支援。

現在所有的資料容器(元件)只需在掛載的時候簡單地呼叫一次 dataFetch() 即可,之後我們的 saga 就會自動接管所有的輪詢工作。非常簡而美吧。


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章