不管是前端开发仍是后端的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的话)。