redux中的異步

關於異步

不管是前端開發仍是後端的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('饅頭');

redux中的異步

用過react的人對redux通常多少都有必定了解,畢竟是做爲react社區最熱門的狀態管理框架,相信很多人也是用過。redux並不能開箱即用,在異步上還須要依賴社區的第三方庫。react

redux-thunk

出自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

redux-observable

基於Rxjs。
233
官方文檔上的說明。我不會,我不知道,再見?...
沒用過,就不作討論了。github

redux-saga

在官網上看了一下,沒錯,這就是我想要的。
文檔:中文官方
須要注意的是,中文的文檔已經落後官方文檔了,看的話推薦官方文檔,我這種英語渣是結合起來看的。若是你的英語夠好,能夠做出一些貢獻,傳送門
最簡單的方式,實現一個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),
  ]);
}

logger
redux-logger的效果。

redux-saga的優勢:
  1. 對異步流優秀的控制,對於文中一開始提到的問題,咱們有更好的解決方式。文檔中給瞭解決方案。

    若是咱們只想獲得最新那個請求的響應(例如,始終顯示最新版本的數據)。咱們可使用 takeLatest輔助函數。
    import { takeLatest } from 'redux-saga'
    
      fetchData () {
        // ...
      }
    
      function* watchFetchData() {
        yield* takeLatest('FETCH_REQUESTED', fetchData)
      }

    在任什麼時候刻 takeLatest 只容許執行一個 fetchData 任務。而且這個任務是最後被啓動的那個。 若是以前已經有一個任務在執行,那以前的這個任務會自動被取消。

  2. 無阻塞的調用,能夠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);
        }
      }
    }

    點擊登錄,未完成請求時,當即點擊登出,狀態變化停留在登出:
    login

redux-saga還有其餘不少功能,如同時執行多個任務等,更多的內容建議看官方的文檔。

其餘的異步方式

redux-promiseredux-promise-middleware這些,不是很推薦。

總結

異步流一直是比較煩人的點,在redux中也是。綜合使用的來看,推薦用redux-saga或者redux-observable(若是熟悉Rxjs的話)。

相關文章
相關標籤/搜索