封装umi-request时通过 AbortController 配置取消请求

一、关键部分

一、在封装的request.ts中

  1. 声明一个 abortControllers 对象用于存储要取消的请求(我用了-s表示复数,多个abortcontroller对象,与下面👇的单个abortController区分)
  2. 封装取消请求的函数cancelRequest, 传入要取消的请求ID ( requestId ) 判断如果在AbortController对象中存在该请求,就可以通过abort来中断
  3. 在请求拦截器中,如果需要让请求可取消:
    1. 创建一个新的AbortController对象
    2. 在AbortController 对象存储这个请求ID,键为请求ID,值为刚创建的 abortController 对象
    3. 将该 abortController 的 signal 对象存到option的signal对象下
    4. 请求时发送option
  4. export { request, cancelRequest }
/**
* 创建一个全局的 AbortController 和 signal 对象, 用于取消请求
*/let abortControllers: { [key: string]: AbortController } = {};let signal: AbortSignal | null = null; // 没用到/**
* 取消当前的请求
*/const cancelRequest = (requestId: string) => {if (abortControllers[requestId]) {abortControllers[requestId].abort();delete abortControllers[requestId];}// if (signal) {//     signal.removeEventListener('abort', () => {});//     signal = null;    // }
};/**
* token拦截器
*/
request.interceptors.request.use((url: string, options: any) => {let newOptions = { ...options };if (options.requestId) {let abortController = new AbortController();// 存储当前请求的 AbortController 对象abortControllers[options.requestId] = abortController;let signal = abortController.signal;newOptions.signal = signal;}// 其他部分。。。。return { url, options: newOptions };
});export { request, cancelRequest };

二、封装调用 request 和 cancelRequest 的 callApi 与 cancelApi

 import qs from 'qs';import { request, cancelRequest } from './request’;interface IConfig {requestId?: string;cancelable?: boolean;}export const callApi = (method: string, path: string, params?: any, config: IConfig = {}) => {const body = ['GET', 'DELETE'].includes(method) ? null : JSON.stringify(params);const urlpath = method === 'GET' && params ? `${path}?${qs.stringify(params)}` : path;return request(urlpath, {method,body,requestId: config?.cancelable ? config.requestId : undefined});};export const cancelApi = (requestId: string) => {cancelRequest(requestId);};

三、调用请求并配置该请求为可取消

 try {const res = await callApi('GET', url, undefined,{  cancelable: true,requestId:  ‘xxx’,  //id可随意配置为任意字符串,只要保证唯一并且取消时能对应上就行 }).then((res) => res);return res;} catch (error) {console.error(error);return {error: {message: 'Error occurred while fetching data'}};
}

四、在合适的地方取消该请求,注意对应上请求ID requestId

cancelApi(‘xxx’);

二、完整代码:

api / request.ts

import { message } from 'antd';
import config from '../config/dev';
import { extend } from 'umi-request';
import { history, useModel } from 'umi';
import { isFormData } from '@/utils/utils';const API_URL = config.apiBase;
const codeMessage = {200: '服务器成功返回请求的数据',201: '新建或修改数据成功',202: '一个请求已经进入后台排队(异步任务)',204: '删除数据成功',400: '请求有误',401: '用户名或密码错误',403: '用户得到授权,但是访问是被禁止的',404: '请求失败,结果不存在',405: '操作失败',406: '请求的格式不可得',410: '请求的资源被永久删除',422: '操作失败',500: '服务器发生错误,请检查服务器',502: '网关错误',503: '服务不可用,服务器暂时过载或维护',504: '网关超时'
};
type mapCode = 200 | 201 | 202 | 204 | 400 | 401 | 403 | 404 | 405 | 406 | 410 | 422 | 500 | 502 | 503 | 504;/**
* 创建一个全局的 AbortController 和 signal 对象, 用于取消请求
*/
let abortControllers: { [key: string]: AbortController } = {};
let signal: AbortSignal | null = null;/**
* 取消当前的请求
*/
const cancelRequest = (requestId: string) => {if (abortControllers[requestId]) {abortControllers[requestId].abort();delete abortControllers[requestId];}// if (signal) {// 	signal.removeEventListener('abort', () => {});// 	signal = null;// }
};/**
* 异常处理程序
*/
const errorHandler = (error: { response: Response; data: any; type: string }): Response | undefined => {const { response, data } = error;// if (data?.error) {// // message.error(data.error.message);// return data;// }if (!response) {if (error.type === 'AbortError') {return;}if (error.type === 'Timeout') {message.error('请求超时,请诊断网络后重试');return;}message.error('无法连接服务器');} else if (response && response.status) {const errorText = codeMessage[response.status as mapCode] || response.statusText;message.error(errorText);}return response;
};/**
* 配置request请求时的默认参数
*/
const request = extend({timeout: 50000,timeoutMessage: '请求超时,请诊断网络后重试',prefix: process.env.NODE_ENV === 'development' ? API_URL : '/api',// prefix: process.env.NODE_ENV === 'development' ? API_URL : 'http://192.168.31.196/api',errorHandler //默认错误处理// credentials: 'include', //默认请求是否带上cookie
});/**
* token拦截器
*/
request.interceptors.request.use((url: string, options: any) => {let newOptions = { ...options };if (options.requestId) {let abortController = new AbortController();// 存储当前请求的 AbortController 对象abortControllers[options.requestId] = abortController;let signal = abortController.signal;newOptions.signal = signal;}const token = localStorage.getItem('token');if (token) {newOptions.headers['Authorization'] = token ? `Bearer ${token}` : null;}newOptions.headers['Content-Type'] = 'application/json';if (isFormData(newOptions.body)) {delete newOptions.headers['Content-Type'];}if (options.content_type) {newOptions.headers['Content-Type'] = options.content_type;delete newOptions['content_type'];}return { url, options: newOptions };
});request.interceptors.response.use((response: any, options: any) => {const token = localStorage.getItem('token');if (response.status === 401 && history.location.pathname === '/login' && options.method === 'POST') {message.error('用户名或密码错误');return;}if (response.status === 401 || response.status === 403 || (!token && history.location.pathname !== '/login')) {message.destroy();message.error('登录已过期,请重新登录');localStorage.removeItem('token');history.push('/login');return;}// 截获返回204的响应,由于后端只返回空字符串'',不便于处理,所以我将其转换为‘204’返回if (response.status === 204) {// message.success(codeMessage[response.status as mapCode]);return '204';}return response;
});export { request, cancelRequest };

api/index.ts中存放的callApi和cancelApi

import qs from 'qs';
import { request, cancelRequest } from './request';
import { IConfig } from '@/constants/interface';export const callApi = (method: string, path: string, params?: any, config: IConfig = {}) => {const body = ['GET', 'DELETE'].includes(method) ? null : JSON.stringify(params);const urlpath = method === 'GET' && params ? `${path}?${qs.stringify(params)}` : path;return request(urlpath, { method, body, requestId: config?.cancelable ? config.requestId : undefined });
};export const cancelApi = (requestId: string) => {cancelRequest(requestId);
};export const uploadApi = (path: string, params?: any) => {const formData = new FormData();Object.keys(params).forEach((item) => {formData.append(item, params[item]);});return request(path, {method: 'POST',body: formData});
};

Interface.ts

export interface IConfig {requestId?: string;cancelable?: boolean;
}

map.ts调用callApi

import { IConfig, IMapSerch, IMapStatistic } from '@/constants/interface';
import { callApi } from '.';
import { API } from './api';const basePath = '/map_search';
export const mapSearch = async (search: string | undefined, config?: IConfig): Promise<API.IResType<IMapSerch>> => {try {const res = await callApi('GET', search ? `${basePath}?search=${search}` : basePath, undefined, config).then((res) => res);return res;} catch (error) {console.error(error);return {error: {message: 'Error occurred while fetching data'}};}
};

页面中pages/map/index.tsx

import { GaodeMap } from '@antv/l7-maps’;
import { useEffect, useState, useRef } from 'react';
import { mapSearch } from '@/api/map';
import { cancelApi } from '@/api';const id = String(Math.random());export default function MapManage() {
const [height, setHeight] = useState<number>(window.innerHeight - 38);
const [mapScene, setScene] = useState<Scene>();useEffect(() => {let scene = new Scene({id,map: new GaodeMap({center: [89.285302, 44.099382],pitch: 0,style: 'normal',zoom: 12,plugin: ['AMap.ToolBar'],WebGLParams: {preserveDrawingBuffer: true}}),logoVisible: false});setScene(scene);scene.on('loaded', async (a) => {//@ts-ignorescene.map.add(new window.AMap.TileLayer.Satellite({ opacity: 0.4, detectRetina: true }));scene.on('moveend', (_) => handleBounds(scene)); // 地图移动结束后触发,包括平移,以及中心点变化的缩放。如地图有拖拽缓动效果,则在缓动结束后触发scene.on('zoomend', (_) => handleBounds(scene)); // 缩放停止时触发// =========加载图层数据==========const data = await fetchDataResult();setHeight(window.innerHeight - 38);});return () => {// 页面卸载前取消请求cancelApi('mapSearch');// @ts-ignorescene.layerService?.stopAnimate();scene.destroy();};
}, []);const fetchDataResult = async (query: string | undefined = undefined) => {const result = await mapSearch(query, {cancelable: true,requestId: 'mapSearch'});return result;
};return (<div><div id={id} style={{ height: height }} /></div>
);
}

三、效果

在这里插入图片描述

四、最后说明

前端取消请求只是停止等待服务器的响应,但并不会通知服务器端停止处理请求,如果服务器端不进行处理,仍然可能会继续占用资源并处理请求,所以,为了更有效地处理取消请求,应该在后端/服务器端也进行相应的处理

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/5624.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

038——基于STM32和I.MX6uLL实现uart控制GPS(失败者总结)

目录 1、GPS模块简介 2、GPS数据格式 3、方案梳理 1、GPS模块简介 全球定位系统(Global Positioning System&#xff0c; GPS)是一种以空中卫星为基础的高精度无线电导航的定位系统&#xff0c;它在全球任何地方以及近地空间都能够提供准确的地理位置、车行速度及精确的时间…

邦注科技 温控箱对企业的重要性

注塑加工是将加热的熔融塑料注入模具中形成所需产品的工艺过程。良好的注塑加工工艺需要控制好许多参数&#xff0c;其中最重要的因素之一就是模具的温度。模具温度的不稳定会导致产品尺寸大小、表面缺陷等方面的问题&#xff0c;甚至会导致生产不良品&#xff0c;加大生产成本…

【webrtc】MessageHandler 5: 基于线程的消息处理:以PeerConnection信令线程为例

peerconn的信令是通过post 消息到自己的信令线程消息来处理的PeerConnectionMessageHandler 是具体的处理器G:\CDN\rtcCli\m98\src\pc\peer_connection_message_handler.hMachinery for handling messages posted to oneself PeerConnectionMessageHandler 明确服务于 signalin…

数据仓库和数据仓库分层

一、数据仓库概念 数据仓库(Data Warehouse)&#xff0c;可简写为DW或DWH。数据仓库&#xff0c;是为企业所有级别的决策制定过程&#xff0c;提供所有类型数据支持的战略集合。它是单个数据存储&#xff0c;出于分析性报告和决策支持目的而创建。 为需要业务智能的企业&#…

CGAL 点云数据生成DSM、DTM、等高线和数据分类

原文链接 CGAL 点云数据生成DSM、DTM、等高线和数据分类 - 知乎 在GIS应用软件中使用的许多传感器(如激光雷达)都会产生密集的点云。这类应用软件通常利用更高级的数据结构&#xff1a;如&#xff1a;不规则三角格网 (TIN)是生成数字高程模型 (DEM) 的基础&#xff0c;也可以利…

2024深圳杯数学建模竞赛A题(东三省数学建模竞赛A题):建立火箭残骸音爆多源定位模型

更新完整代码和成品完整论文 《2024深圳杯&东三省数学建模思路代码成品论文》↓↓↓&#xff08;浏览器打开&#xff09; https://www.yuque.com/u42168770/qv6z0d/zx70edxvbv7rheu7?singleDoc# 2024深圳杯数学建模竞赛A题&#xff08;东三省数学建模竞赛A题&#xff0…

PyVista 3D数据可视化 Python 库 简介 含源码

Pyvista是一个用于科学可视化和分析的Python库 &#xff1b;我认为它适合做一些网格数据的处理&#xff1b; 它封装了VTK&#xff08;Visualization Toolkit&#xff09;之上&#xff0c;提供了一些高级接口&#xff0c; 3D数据可视化变得更加简单和易用。 1.安装 pyvista&…

开发一个语音聊天社交app小程序H5需要多少钱?

社交&#xff0c;即时通讯APP系统。如何开发一个社交App||开发一个即时通信应用是一项复杂而充满挑战的任务&#xff0c;需要考虑多个技术、开发时间和功能方面的因素。以下是一个概要&#xff0c;描述了从技术、开发时间和功能角度如何开发这样的应用&#xff1a; 1. 技术要点…

70.网络游戏逆向分析与漏洞攻防-角色与怪物信息的更新-整理与角色数据更新有关的数据

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 如果看不懂、不知道现在做的什么&#xff0c;那就跟着做完看效果 现在的代码都是依据数据包来写的&#xff0c;如果看不懂代码&#xff0c;就说明没看懂数据包…

基于python的舞蹈经验分享交流网站django+vue

1.运行环境&#xff1a;python3.7/python3.8。 2.IDE环境&#xff1a;pycharmmysql5.7/8.0; 3.数据库工具&#xff1a;Navicat11 4.硬件环境&#xff1a;windows11/10 8G内存以上 5.数据库&#xff1a;MySql 5.7/8.0版本&#xff1b; 运行成功后&#xff0c;在浏览器中输入&am…

新唐的nuc980/nuc972的开发3-官方源码编译

上一节中bsp已经安装&#xff0c;交叉环境已经搭建&#xff0c;理应就可以正常的编写上层的应用程序啦。 但是系统启动次序是- uboot-> kernel内核 ->挂载文件系统 ->上层应用程序 下面是bsp安装后的文件&#xff1a; 因此本章节&#xff0c;将讲解 uboot-> kerne…

刷代码随想录有感(51):从中序和后序前序和中序构造二叉树

中后题干&#xff1a; 第一步&#xff1a;如果数组大小为零的话&#xff0c;说明是空节点了。 第二步&#xff1a;如果不为空&#xff0c;那么取后序数组最后一个元素作为节点元素。 第三步&#xff1a;找到后序数组最后一个元素在中序数组的位置&#xff0c;作为切割点 第四…

Large Language Models for Test-Free Fault Localization

基本信息 这是24年2月发表在ICSE 24会议&#xff08;CCF A&#xff09;的一篇文章&#xff0c;作者团队来自美国卡内基梅隆大学。 博客创建者 武松 作者 Aidan Z.H. Yang&#xff0c;Claire Le Goues&#xff0c;Ruben Martins&#xff0c;Vincent J. Hellendoorn 标签 …

启明云端2.4寸屏+ESP32-S3+小型智能调速电动家用除草机案例 触控三档调速,能显示电压故障码

今天给大家分享个启明云端2.4寸屏ESP32-S3小型智能调速电动家用除草机案例&#xff0c;国外有草坪文化&#xff0c;这个机器能智能触控三档调速&#xff0c;带屏能显示电压故障码&#xff0c;数显档位&#xff08;3档最大&#xff09;&#xff0c;触控屏&#xff0c;长按3秒就能…

使用 langchain 连接 通义千问 并用 fastApi 开放接口

安装 langchain 方法 https://www.cnblogs.com/hailexuexi/p/18087602 安装 fastapi fastapi 是一个用于构建高性能 Web 应用的 Python 框架&#xff0c;它提供了简洁、高效的 API 开发体验。 pip install fastapi 安装 uvicorn uvicorn 是一个用于运行 FastAPI 应用的服务…

C语言学习/复习37--进阶总结与题目练习

一、题目练习 1. 循环与无符号char的取值范围 注意事项&#xff1a;0~255 -128~127 char类的取值范围看做循环图 2.ASCLL值与循环 3.按位操作与bit位 4 .结构体的大小 注意事项&#xff1a;结构体嵌套结构体的大小计算 5.循环条件 6.数据类型与原反补码 7.指针访问字符串数…

商城系统推荐,如何找到一款可靠的商城系统?

如今&#xff0c;电商系统成为商家必不可少的营销工具&#xff0c;其系统在金融、外贸、零售等行业领域应用广泛。那么&#xff0c;作为初试水的企业又没有挑选电商系统的经验&#xff0c;如何找到拥有全功能、全渠道、可靠的网上商城系统呢&#xff1f; 我们可以先按电商系统…

【Vue 2.x】学习vue之三路由

文章目录 Vue三路由第十章1、vue中的路由vue的应用分为a、多页面应用b、单页面应用 2、路由的基本应用1、基础2、使用3、加载 3、vue组件的分类1、普通组件2、路由组件 4、路由的嵌套5、路由传递Query参数1、拼接参数传递2、路由传递对象 6、简化路由1、命名路由 7、parms传递参…

力扣82-链表、迭代 的思考

题目解读 给定一个已排序的链表的头 head &#xff0c; 删除原始链表中所有重复数字的节点&#xff0c;只留下不同的数字 。返回 已排序的链表 。 两个示范 思考 返回链表&#xff1a;返回更新链表后的头结点&#xff1b; 更新链表&#xff1a;判断重复元素&#xff0c;改变指针…

Day56|动态规划part16:583. 两个字符串的删除操作、72. 编辑距离、编辑距离总结篇

583. 两个字符串的删除操作 我的方法&#xff0c;先求出两者的最长公共子序列长度&#xff0c;再用两个字符串的长度相减就是两者分别要做操作的步数&#xff1a; class Solution {public int minDistance(String word1, String word2) {int[][] dp new int[word1.length() …