使用AbortController终止Axios请求后重复请求报CanceledError问题
解决React中取消请求后再次发起触发CanceledError的问题
问题根源在于你复用了同一个AbortController实例——一旦调用abort(),该控制器的signal就会进入终止状态,后续请求再使用这个signal会直接触发取消错误。
修复步骤:
- 每次发起请求时创建新的AbortController:确保每次请求都使用全新的可正常工作的控制器实例,避免复用已终止的实例。
- 区分主动取消与其他错误:在异常捕获逻辑中过滤主动取消的错误,避免这类情况输出不必要的日志。
修改后的完整代码:
import { FC, useRef, useState } from 'react' import clsx from 'clsx' import { useAppSelector } from '../../store/hooks' import { Button } from '@gravity-ui/uikit' import { Modal } from '@gravity-ui/uikit' import { Spin } from '@gravity-ui/uikit' import avatarImg from '../../images/avatar.png' import styles from './Profile.module.sass' import axios, { AxiosError } from 'axios' import { IQuote } from '../../models/UserModels' interface ProfileProps { className?: string } export const Profile: FC<ProfileProps> = ({ className }) => { const [open, setOpen] = useState<boolean>(false) const [isLoading, setIsLoading] = useState<boolean>(false) const [qoute, setQoute] = useState<string>('') const [author, setAuthor] = useState<string>('') const controller = useRef<AbortController | null>(null) const { name } = useAppSelector((state) => state.userSlice.user) const handleCancelRequest = () => { setIsLoading(false) controller.current?.abort() setOpen(false) } const handleUpdateRequest = async () => { setIsLoading(true) setOpen(true) // 创建新的AbortController实例,替换旧的终止实例 controller.current = new AbortController() try { const response = await axios.get<IQuote[]>( 'https://api.api-ninjas.com/v1/quotes', { headers: { 'X-Api-Key': 'Zhua2p7EK+yMxOiInSRp6Q==04lsXHbDKvmHe5kZ', }, signal: controller.current.signal, } ) setQoute(response.data[0].quote) setAuthor(response.data[0].author) setIsLoading(false) setOpen(false) } catch (error: AxiosError | unknown) { setIsLoading(false) setOpen(false) // 仅处理非主动取消的错误 if (error instanceof AxiosError && error.code !== 'ERR_CANCELED') { console.error(error) } } } return ( <div className={clsx(className, styles.wrapper)}> <div className={styles.userInfo}> <div className={styles.avatar}> <img src={avatarImg} alt='avatar' /> </div> <div className={styles.greetings}> Welcome, {name || localStorage.getItem('name')} <Button onClick={handleUpdateRequest} size='xl'> Update </Button> </div> </div> {author && ( <p className={styles.subtext}> <span className={styles.author}>{author}</span> -{' '} <span>{qoute} ©</span> </p> )} <Modal open={open} onOpenChange={setOpen}> <div className={styles.modal}> <h3 className={styles.modalTitle}>Requesting the quote</h3> <div className={styles.modalContent}> <span> Step 1 - Requesting author... {isLoading && <Spin className={styles.spinner} size='xs' />} </span> <span> Step 2 - Requesting quote... {isLoading && <Spin className={styles.spinner} size='xs' />} </span> </div> <Button onClick={handleCancelRequest} size='m'> Cancel </Button> </div> </Modal> </div> ) }
关键修改说明:
- 把
controller初始值设为null,每次发起请求时生成新的AbortController实例 - 取消请求时用可选链调用
abort(),避免初始状态为null时的报错 - 捕获异常时判断错误码,主动取消的错误不输出日志
- 无论请求成功或失败,都重置
isLoading和弹窗状态,保证UI一致性
内容的提问来源于stack exchange,提问作者Akaev Dzhalal
相关产品推荐
相关产品推荐

