不管是前端開發仍是後端的nodejs,異步基本上老是核心。我所在的公司是作大數據處理的,常常須要從後臺拿取大量的數據,致使整個請求會比較耗時。以下一個場景:
我選擇A應用,點擊搜索,這時候我立刻去選擇應用B,點擊搜索,可能的狀況是,A的數據量比B的數據量大,致使A的結果是你最後獲取到的,這是就會出現這樣的問題:此時用戶的搜索條件是B,但你展現的結果確是A。代碼是相似這樣的:html
const request = (api, time) => { return new Promise(resolve => { setTimeout(() => resolve(`data ${api}!`), time); }) }; const requestA = () => request('A', 4000); const requestB = () => request('B', 2000); let result; requestA().then(data => { result = data; }); // 接着 requestB().then(data => { result = data; }); // 4秒後打印result console.log(result); // 打印出 'data A!'
實際工做中,比較簡單的方式大概就是,在請求A的時候給查詢按鈕設置成disabled
,請求返回後再去放開按鈕,去請求B。可是體驗可能會不太好,好比用戶搜了A,A的搜索條件比較廣,致使用戶一直等到timeout
的狀況才能去繼續查詢。
咱們能夠用閉包去解決這個問題:前端
let _id = 0; let _req_id = 0; const request = (api, time) => { _id++; return new Promise(resolve => { setTimeout(() => resolve(`data ${api}!`), time); }) }; const requestA = () => request('A', 4000); const requestB = () => request('B', 2000); let result; void function(id) { requestA().then(data => { if (id === _id) { result = data; } }); }(++_req_id); // 接着 void function(id) { requestB().then(data => { if (id === _id) { result = data; } }); }(++_req_id); // 4秒後打印result console.log(result); // 打印出 'data B!'
我在angular1.5
中碰到這種問題通常都是這樣解決的。
(之後面試官問你閉包都有啥用的時候能夠舉這個栗子,比那些星期一吃包子,星期二吃饅頭的栗子好多了2333)node
const plan = day => food => console.log(`${day}吃${food}`); const SundayPlan = plan('星期天'); SundayPlan('包子'); SundayPlan('饅頭');
用過react
的人對redux
通常多少都有必定了解,畢竟是做爲react
社區最熱門的狀態管理框架,相信很多人也是用過。redux
並不能開箱即用,在異步上還須要依賴社區的第三方庫。react
出自redux
的做者Dan,相信這個很多人都使用,相對於其餘方案,使用起來比較簡單。對於不太複雜的場景使用起來仍是很方便的。使用起來就像下面的樣子:ios
const GET_TOPICS_REQUEST = 'GET_TOPICS_REQUEST', GET_TOPICS_SUCCESS = 'GET_TOPICS_SUCCESS', GET_TOPICS_FAILED = 'GET_TOPICS_FAILED'; export const getTopics = (query = defaultQuery) => (dispatch) => { dispatch({ type: GET_TOPICS_REQUEST, isPending: true, }); axios.get('').then(() => { dispatch({ type: GET_TOPICS_SUCCESS, isPending: false, }); }).catch(err => { dispatch({ type: GET_TOPICS_FAILED, isPending: false, }); }); }
使用起來至關簡單,但其中會出現像咱們剛開始提到的問題,action是無法取消的。打個比方,我先請求A,而後馬上請求B,是相同的action,B的數據先返回,A的數據後返回,最後state更新的數據就變成了A。可行的解決方式是 redux-thunk + async/await。但async/await
這玩意用在前端,如今不是很推薦。git
基於Rxjs。
官方文檔上的說明。我不會,我不知道,再見?...
沒用過,就不作討論了。github
在官網上看了一下,沒錯,這就是我想要的。
文檔:中文,官方。
須要注意的是,中文的文檔已經落後官方文檔了,看的話推薦官方文檔,我這種英語渣是結合起來看的。若是你的英語夠好,能夠做出一些貢獻,傳送門。
最簡單的方式,實現一個redux-logger:面試
// StoreConfig import { createStore, compose, applyMiddleware } from 'redux'; import createSagaMiddleware, { END } from 'redux-saga'; import { xx } from '../reducers'; const StoreConfig = (initialState) => { const sagaMiddleware = createSagaMiddleware(); const store = createStore( xx, initialState, compose( applyMiddleware(sagaMiddleware), window['devToolsExtension'] ? window['devToolsExtension']() : f => f, ), ); store.runSaga = sagaMiddleware.run; store.close = () => store.dispatch(END); return store; }; export default StoreConfig;
啓動redux-saga:redux
import rootSaga from './sagas'; import StoreConfig from './store/index'; const store = StoreConfig(initialState); store.runSaga(rootSaga);
saga/index.js:axios
import { take, all, fork, select } from 'redux-saga/effects'; import { api } from '../services'; function* watchAndLog() { while (true) { const action = yield take('*'); const getState = yield select(state => state); console.log('%caction---', 'color: green;', action); console.log('%cstate after---', 'color: green;', getState); } } export default function* root() { yield all([ fork(watchAndLog), ]); }
redux-logger的效果。
redux-saga的優勢:
對異步流優秀的控制,對於文中一開始提到的問題,咱們有更好的解決方式。文檔中給瞭解決方案。
若是咱們只想獲得最新那個請求的響應(例如,始終顯示最新版本的數據)。咱們可使用
takeLatest
輔助函數。
import { takeLatest } from 'redux-saga' fetchData () { // ... } function* watchFetchData() { yield* takeLatest('FETCH_REQUESTED', fetchData) }
在任什麼時候刻 takeLatest 只容許執行一個 fetchData 任務。而且這個任務是最後被啓動的那個。 若是以前已經有一個任務在執行,那以前的這個任務會自動被取消。
無阻塞的調用,能夠fork
一個獨立的「線程」,並能夠經過cancel
取消。如登錄,在登錄未完成時點擊登出,將登錄的線程取消:
function* authorize(username, password) { try { const { token } = yield call(api.userLogin, { username, password }); yield put({type: LOGIN_SUCCESS, isLoginPending: false, token }); localStorage.setItem('SAGA-TOKEN', token); } catch (error) { yield put({type: LOGIN_FAILURE, isLoginPending: false, error}); } finally { if (yield cancelled()) { console.log('%cwow, killed the task!', 'color: red;'); } } } function* loginFlow() { while (true) { const { username, password } = yield take(LOGIN_REQUEST); const task = yield fork(authorize, username, password); const action = yield take([LOGIN_OUT, LOGIN_FAILURE]); if (action.type === LOGIN_OUT) { localStorage.removeItem('SAGA-TOKEN'); yield cancel(task); } } }
點擊登錄,未完成請求時,當即點擊登出,狀態變化停留在登出:
redux-saga
還有其餘不少功能,如同時執行多個任務等,更多的內容建議看官方的文檔。
如redux-promise
、redux-promise-middleware
這些,不是很推薦。
異步流一直是比較煩人的點,在redux中也是。綜合使用的來看,推薦用redux-saga
或者redux-observable
(若是熟悉Rxjs的話)。