React中createAsyncThunk的Abort失效时如何正确中止异步action
React中正确中止createAsyncThunk触发Action的方案
直接调用abort不生效基本都是用法不符合Redux Toolkit的设计逻辑,按下面的步骤写就能正常中止:
核心实现逻辑
createAsyncThunk内置了AbortController能力,不需要你手动实例化AbortController,只要正确拿到对应请求的abort方法、并且在异步逻辑中绑定中断信号即可。
保存每次dispatch返回的thunk引用
dispatch一个createAsyncThunk生成的action时,返回值是一个带abort()方法的promise,这是唯一能中断对应那次请求的合法方法,不要自己额外创建控制器实例。
组件中用ref存储这个引用,避免重渲染导致引用丢失:import { useDispatch } from 'react-redux'; import { fetchData } from './featureSlice'; import { useRef, useEffect } from 'react'; export default function DataList() { const dispatch = useDispatch(); const currentThunk = useRef(null); // 组件挂载发请求,卸载自动中止 useEffect(() => { currentThunk.current = dispatch(fetchData({ page: 1 })); return () => { currentThunk.current?.abort(); }; }, [dispatch]); // 手动触发中止 const handleAbortClick = () => { currentThunk.current?.abort(); }; return <button onClick={handleAbortClick}>取消请求</button>; }thunk内部必须绑定内置的abort信号
90%的abort不生效问题都是漏了这步:createAsyncThunk的payload回调第二个参数里自带signal信号,必须把这个信号传给你用的请求方法,不然外层调abort只会修改redux的状态,根本不会中断实际的网络请求。
正确的thunk写法示例:import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; export const fetchData = createAsyncThunk( 'data/fetchList', async (params, { signal }) => { // fetch原生支持传入signal const resp = await fetch('/api/list', { signal, method: 'GET', }); // axios也支持直接传signal参数 // const resp = await axios.get('/api/list', { params, signal }) return resp.json(); } );
常见踩坑点
- 用普通组件变量存thunk引用,重渲染后引用被重置,调用abort时指向的不是当前正在跑的请求
- 并发多个相同thunk请求时只存了最后一个的引用,前面的请求无法单独中止,这种场景可以用Map结构按请求唯一标识存储对应thunk引用
- 自己手动new AbortController把signal传到thunk里,和thunk内置的signal不匹配,导致abort触发不了
- 异步逻辑里写了setTimeout、长轮询等自定义异步任务,没有监听signal的abort事件做清理,这类非请求类的异步逻辑需要自己加监听:
async (params, { signal }) => { return new Promise((resolve, reject) => { const timer = setTimeout(() => { resolve('done') }, 3000); // 监听abort事件清理定时器 signal.addEventListener('abort', () => { clearTimeout(timer); reject(new DOMException('Aborted', 'AbortError')); }) }) }
中止后的状态兼容
thunk被abort后会走rejected分支,错误对象的name固定为AbortError,可以在reducer里单独区分这种场景,不要把主动中止当成正常请求错误弹提示:
const dataSlice = createSlice({ name: 'data', initialState: { list: [], loading: false, errorMsg: '' }, extraReducers: builder => { builder .addCase(fetchData.pending, state => { state.loading = true; state.errorMsg = ''; }) .addCase(fetchData.fulfilled, (state, action) => { state.loading = false; state.list = action.payload; }) .addCase(fetchData.rejected, (state, action) => { state.loading = false; // 主动中止不处理错误 if (action.error.name === 'AbortError') return; state.errorMsg = action.error.message || '请求失败'; }) } })
内容的提问来源于stack exchange,提问作者Yash Bhikadiya
相关产品推荐
相关产品推荐

