在公司实习的时候学到了不少新东西,不如简单记录一些
1 取消异步请求带来的思考
最近实习的时候遇到了这样的一个需求,发送请求A之后,浏览器还有可能异步发送相同接口的请求A1,由于服务端计算的时间长短可能不一样,导致返回的顺序不一定与请求发送的顺序一致,导致界面显示的数据与最后一次请求所期望的结果不一样。
- 甩锅给服务端
让服务端每次返回都带上一个id,通过id去匹配返回的结果是否为最后一次查询所预期的。我想这样能解决问题,但是隐约感觉到能在不修改服务端代码的情况下解决问题,于是就开始瞎折腾。 - 尝试用debounce
最开始没有深入思考这个问题,简单的认为用debounce来保证短时间内多次操作只会发送一次请求。后来发现并不是那么简单,在debounce设置的延迟内服务端可能还没返回结果,若是此时浏览器又发送了新的请求,依然会出现最开始的问题。 - 发送请求后将对应的界面disable掉
可以在发送请求,返回结果之前将对应触发请求的界面给disable掉,但是我想这样有两个缺点:1.对用户不够友好,一个误操作可能就要等到返回结果才能重新操作。2.并不是通用的解决方法,只适用于用户操作触发事件的情况。 不知道叫什么的解决方法
想了很久以后决定去问问导师,导师告诉我他之前也遇到过这样的问题,给我讲了一下他的大致思路:- 将原方法外面包装两层,设置一个
cancelHandler
,初始为null,每次发送请求会返回一个把isCancel
变量置为ture的函数,将这个函数赋值给cancelHandler
,只要cancelHandler
不为null(说明之前发送过请求了),就执行cancelHandler
来将isCancel
置为ture。之后再执行第二层包装的方法。 - 第二层方法先创建一个变量
isCancel
并初始化为false,该变量用于判断是否处理response。然后创建了一个succsessCallback
函数,该函数会作为原本发送请求的方法(成功时)的回调函数,判断isCancel
的值来决定处不处理response。之后创建了一个cancaelCallback
函数作为第二层包装的返回值,也就是之前那一步里赋值给cancelHandler
的函数。之后在执行原本的请求,回调调用successCallback
函数来判断isCancel
以决定是否对response
进行处理。最后第二层包装返回cancelCallback
函数。 - 当第一个请求发送之后,第二个请求事件触发时,此时
cancelHandler
不为null,会去将isCancel
置为ture,这样就不会处理第一次请求的response。 - 前面一大段简单点说就是根据有没有新的请求事件触发来决定是否处理上一次请求的response。
12345678910111213141516171819202122232425262728293031323334//在一个React组件的内部class Example extends React.Component {constructor{//balabalathis.cancelHandler = null; //初始化cancelHandler}//包在最外层的方法,触发发送请求时,调用此方法startFetchData (params) {this.cancelHandler && this.cancelHandler();this.cancelHandler = this.fetchData(params);}//第二层包装,其中realFetchData()是最开始去发送请求的方法fetchData (params) {let isCancel = false;let successCallback = (res) => {if(isCancel) return;//接下来进行原本对response进行的操作//...};let cancelCallback = () => {isCancel = true;};realFetchData(params).then(successCallback);return cancelCallback;}//实际会这样调用onSomethingClick () {//...this.startFetchData(params);}}于是我很顺利的就解决了这个问题。但是这样还是有着一些缺点和值得思考的点,比如依然会发送一次请求,比如如何进行异常处理最合适,能不能用
async/await
让代码看起来更简单等等。- 将原方法外面包装两层,设置一个
redux-sage
(偷懒直接拿来官方文档的例子改了改)12345678910111213141516171819202122232425262728293031import { take, put, call, fork, cancel, cancelled } from 'redux-saga/effects';import { delay } from 'redux-saga';import * as Api from './api';import {action} from './action';function* bgSync() {try {while (true) {yield put(actions.requestStart())const result = yield call(Api.fetchData)yield put(actions.requestSuccess(result))yield call(delay, 5000)}} finally {if (yield cancelled())yield put(actions.requestFailure('Sync cancelled!'))}}function* main() {while ( yield take(START_BACKGROUND_SYNC) ) {// starts the task in the backgroundconst bgSyncTask = yield fork(bgSync)// wait for the user stop actionyield take(STOP_BACKGROUND_SYNC)// user clicked stop. cancel the background task// this will cause the forked bgSync task to jump into its finally blockyield cancel(bgSyncTask)}}redux-observable
redux-observable的takeUtils()
能取消发送的请求,
同时能使用rxjs
的一些运算符,还能异常处理123456789101112131415161718192021222324import * as Api from './api';//假设我们的api请求函数返回的都是Promiseimport 'rxjs';import { Observable } from 'rxjs/Observable';//Action typeconst FETCH_DATA = 'FETCH_DATA';const FETCH_DATA_FULFILLED = 'FETCH_DATA_FULFILLED';const FETCH_DATA_FAILURE = 'FETCH_DATA_FAILURE';const FETCH_DATA_CANCELLED = 'FETCH_DATA_CANCELLED';//Action creatorconst fetchData = (params) => ({type: FETCH_DATA, payload: params});const fetchDataFullfilled = payload => ({type: FETCH_DATA_FULFILLED, payload});const fetchDataFailure = error => ({type: FETCH_DATA_FAILURE, payload: error});const cancelFetchData = () => ({type: FETCH_DATA_CANCELLED});//Epicexport const fetchDataEpic = action$ =>action$.ofType(FETCH_DATA).mergeMap(action =>Observable.fromPromise(Api.fetchDataAjax({...action.payload})).map(response => fetchDataFullfilled(response)).takeUntil(action$.ofType(FETCH_DATA_CANCELLED)).catch(error => Observable.of(fetchDataFailure(error))));