为了账号安全,请及时绑定邮箱和手机立即绑定

createAsyncThunk:中止先前的请求

createAsyncThunk:中止先前的请求

慕勒3428872 2023-07-06 17:36:25
我用来createAsyncThunk向某些 API 发出异步请求。在任何给定时刻只能有一个请求处于活动状态。AbortSignal据我所知,如果我从上次调用中返回了 Promise,则可以使用提供的方法中止该请求。问题是,thunk 本身能否以某种方式“自主”中止先前的请求?我正在考虑两种选择:将最后一个 AbortSignal 保持在状态中。似乎是错误的,因为状态应该是可序列化的。将最后一个 Promise 及其 AbortSignal 保留在全局变量中。似乎也是错误的,因为,你知道,全局变量。有任何想法吗?谢谢。
查看完整描述

2 回答

?
慕森卡

TA贡献1806条经验 获得超8个赞

我不知道您的特定 api 是如何工作的,但下面是一个工作示例,说明如何将中止逻辑放入操作和减速器中,当进行新的提取时,它将中止任何先前活动的假提取:


import * as React from 'react';

import ReactDOM from 'react-dom';

import {

  createStore,

  applyMiddleware,

  compose,

} from 'redux';

import {

  Provider,

  useDispatch,

  useSelector,

} from 'react-redux';

import {

  createAsyncThunk,

  createSlice,

} from '@reduxjs/toolkit';


const initialState = {

  entities: [],

};

// constant value to reject with if aborted

const ABORT = 'ABORT';

// fake signal constructor

function Signal() {

  this.listener = () => undefined;

  this.abort = function () {

    this.listener();

  };

}

const fakeFetch = (signal, result, time) =>

  new Promise((resolve, reject) => {

    const timer = setTimeout(() => resolve(result), time);

    signal.listener = () => {

      clearTimeout(timer);

      reject(ABORT);

    };

  });

// will abort previous active request if there is one

const latest = (fn) => {

  let previous = false;

  return (signal, result, time) => {

    if (previous) {

      previous.abort();

    }

    previous = signal;

    return fn(signal, result, time).finally(() => {

      //reset previous

      previous = false;

    });

  };

};

// fake fetch that will abort previous active is there is one

const latestFakeFetch = latest(fakeFetch);


const fetchUserById = createAsyncThunk(

  'users/fetchByIdStatus',

  async ({ id, time }) => {

    const response = await latestFakeFetch(

      new Signal(),

      id,

      time

    );

    return response;

  }

);

const usersSlice = createSlice({

  name: 'users',

  initialState: { entities: [], loading: 'idle' },

  reducers: {},

  extraReducers: {

    [fetchUserById.fulfilled]: (state, action) => {

      state.entities.push(action.payload);

    },

    [fetchUserById.rejected]: (state, action) => {

      if (action?.error?.message === ABORT) {

        //do nothing

      }

    },

  },

});


const reducer = usersSlice.reducer;

//creating store with redux dev tools

const composeEnhancers =

  window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;

const store = createStore(

  reducer,

  initialState,

  composeEnhancers(

    applyMiddleware(

      ({ dispatch, getState }) => (next) => (action) =>

        typeof action === 'function'

          ? action(dispatch, getState)

          : next(action)

    )

  )

);

const App = () => {

  const dispatch = useDispatch();

  React.useEffect(() => {

    //this will be aborted as soon as the next request is made

    dispatch(

      fetchUserById({ id: 'will abort', time: 200 })

    );

    dispatch(fetchUserById({ id: 'ok', time: 100 }));

  }, [dispatch]);

  return 'hello';

};


ReactDOM.render(

  <Provider store={store}>

    <App />

  </Provider>,

  document.getElementById('root')

);

如果您只需要解决一个承诺(如果它是最新请求的承诺),而无需中止或取消正在进行的承诺(如果不是最新的则忽略解决),那么您可以执行以下操作:


const REPLACED_BY_NEWER = 'REPLACED_BY_NEWER';

const resolveLatest = (fn) => {

  const shared = {};

  return (...args) => {

    //set shared.current to a unique object reference

    const current = {};

    shared.current = current;

    fn(...args).then((resolve) => {

      //see if object reference has changed

      //  if so it was replaced by a newer one

      if (shared.current !== current) {

        return Promise.reject(REPLACED_BY_NEWER);

      }

      return resolve;

    });

  };

};

这个答案演示了如何使用它



查看完整回答
反对 回复 2023-07-06
?
天涯尽头无女友

TA贡献1831条经验 获得超9个赞

我能够将其放在一起,但它非常复杂。


以下函数创建执行实际工作的“内部”异步 thunk,以及委托给内部异步 thunk 并中止之前的调度(如果有)的“外部”异步 thunk。


内部 thunk 的有效负载创建者也被包装为:1)等待有效负载创建者的先前调用完成,2)如果操作在等待期间中止,则跳过调用真正的有效负载创建者(以及 API 调用)。


import { createAsyncThunk, AsyncThunk, AsyncThunkPayloadCreator, unwrapResult } from '@reduxjs/toolkit';


export function createNonConcurrentAsyncThunk<Returned, ThunkArg>(

  typePrefix: string,

  payloadCreator: AsyncThunkPayloadCreator<Returned, ThunkArg>,

  options?: Parameters<typeof createAsyncThunk>[2]

): AsyncThunk<Returned, ThunkArg, unknown> {

  let pending: {

    payloadPromise?: Promise<unknown>;

    actionAbort?: () => void;

  } = {};


  const wrappedPayloadCreator: AsyncThunkPayloadCreator<Returned, ThunkArg> = (arg, thunkAPI) => {

    const run = () => {

      if (thunkAPI.signal.aborted) {

        return thunkAPI.rejectWithValue({name: 'AbortError', message: 'Aborted'});

      }

      const promise = Promise.resolve(payloadCreator(arg, thunkAPI)).finally(() => {

        if (pending.payloadPromise === promise) {

          pending.payloadPromise = null;

        }

      });

      return pending.payloadPromise = promise;

    }


    if (pending.payloadPromise) {

      return pending.payloadPromise = pending.payloadPromise.then(run, run); // don't use finally(), replace result

    } else {

      return run();

    }

  };


  const internalThunk = createAsyncThunk(typePrefix + '-protected', wrappedPayloadCreator);


  return createAsyncThunk<Returned, ThunkArg>(

    typePrefix,

    async (arg, thunkAPI) => {

      if (pending.actionAbort) {

        pending.actionAbort();

      }

      const internalPromise = thunkAPI.dispatch(internalThunk(arg));

      const abort = internalPromise.abort;

      pending.actionAbort = abort;

      return internalPromise

        .then(unwrapResult)

        .finally(() => {

          if (pending.actionAbort === abort) {

            pending.actionAbort = null;

          }

        });

    },

    options

  );

}



查看完整回答
反对 回复 2023-07-06
  • 2 回答
  • 0 关注
  • 115 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信