请求合并有什么意义?一文详解

Java 请求合并技术:十倍提升系统性能 想让系统性能提升?Java请求合并技术将分散的接口调用打包处理,大幅减少网络往返与资源消耗,轻松应对高并发场景。本文详解实现原理与代码,助你系统性能翻倍提升! 阅读详情

请求合并到底有什么意义呢?我们来看下图。

图片

假设我们3个用户(用户id分别是1、2、3),现在他们都要查询自己的基本信息,请求到服务器,服务器端请求数据库,发出3次请求。我们都知道数据库连接资源是相当宝贵的,那么我们怎么尽可能节省连接资源呢?

这里把数据库换成被调用的远程服务,也是同样的道理。

我们改变下思路,如下图所示。

图片

我们在服务器端把请求合并,只发出一条SQL查询数据库,数据库返回后,服务器端处理返回数据,根据一个唯一请求ID,把数据分组,返回给对应用户。

技术手段

  • LinkedBlockQueue 阻塞队列

  • ScheduledThreadPoolExecutor 定时任务线程池

  • CompleteableFuture future 阻塞机制(Java 8 的 CompletableFuture 并没有 timeout 机制,后面优化,使用了队列替代)

代码实现

查询用户的代码

public interface UserService {

    Map<String, Users> queryUserByIdBatch(List<UserWrapBatchService.Request> userReqs);
}
@Service
public class UserServiceImpl implements UserService {

    @Resource
    private UsersMapper usersMapper;

    @Override
    public Map<String, Users> queryUserByIdBatch(List<UserWrapBatchService.Request> userReqs) {
        // 全部参数
        List<Long> userIds = userReqs.stream().map(UserWrapBatchService.Request::getUserId).collect(Collectors.toList());
        QueryWrapper<Users> queryWrapper = new QueryWrapper<>();
        // 用in语句合并成一条SQL,避免多次请求数据库的IO
        queryWrapper.in("id", userIds);
        List<Users> users = usersMapper.selectList(queryWrapper);
        Map<Long, List<Users>> userGroup = users.stream().collect(Collectors.groupingBy(Users::getId));
        HashMap<String, Users> result = new HashMap<>();
        userReqs.forEach(val -> {
            List<Users> usersList = userGroup.get(val.getUserId());
            if (!CollectionUtils.isEmpty(usersList)) {
                result.put(val.getRequestId(), usersList.get(0));
            } else {
                // 表示没数据
                result.put(val.getRequestId(), null);
            }
        });
        return result;
    }
}

合并请求的实现

package com.springboot.sample.service.impl;

import com.springboot.sample.bean.Users;
import com.springboot.sample.service.UserService;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.*;

/***
 * zzq
 * 包装成批量执行的地方
 * */
@Service
public class UserWrapBatchService {
    @Resource
    private UserService userService;

    /**
     * 最大任务数
     **/
    public static int MAX_TASK_NUM = 100;


    /**
     * 请求类,code为查询的共同特征,例如查询商品,通过不同id的来区分
     * CompletableFuture将处理结果返回
     */
    public class Request {
        // 请求id 唯一
        String requestId;
        // 参数
        Long userId;
        //TODO Java 8 的 CompletableFuture 并没有 timeout 机制
        CompletableFuture<Users> completableFuture;

        public String getRequestId() {
            return requestId;
        }

        public void setRequestId(String requestId) {
            this.requestId = requestId;
        }

        public Long getUserId() {
            return userId;
        }

        public void setUserId(Long userId) {
            this.userId = userId;
        }

        public CompletableFuture getCompletableFuture() {
            return completableFuture;
        }

        public void setCompletableFuture(CompletableFuture completableFuture) {
            this.completableFuture = completableFuture;
        }
    }

    /*
    LinkedBlockingQueue是一个阻塞的队列,内部采用链表的结果,通过两个ReenTrantLock来保证线程安全
    LinkedBlockingQueue与ArrayBlockingQueue的区别
    ArrayBlockingQueue默认指定了长度,而LinkedBlockingQueue的默认长度是Integer.MAX_VALUE,也就是无界队列,在移除的速度小于添加的速度时,容易造成OOM。
    ArrayBlockingQueue的存储容器是数组,而LinkedBlockingQueue是存储容器是链表
    两者的实现队列添加或移除的锁不一样,ArrayBlockingQueue实现的队列中的锁是没有分离的,即添加操作和移除操作采用的同一个ReenterLock锁,
    而LinkedBlockingQueue实现的队列中的锁是分离的,其添加采用的是putLock,移除采用的则是takeLock,这样能大大提高队列的吞吐量,
    也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。
     */
    private final Queue<Request> queue = new LinkedBlockingQueue();

    @PostConstruct
    public void init() {
        //定时任务线程池,创建一个支持定时、周期性或延时任务的限定线程数目(这里传入的是1)的线程池
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);

        scheduledExecutorService.scheduleAtFixedRate(() -> {
            int size = queue.size();
            //如果队列没数据,表示这段时间没有请求,直接返回
            if (size == 0) {
                return;
            }
            List<Request> list = new ArrayList<>();
            System.out.println("合并了 [" + size + "] 个请求");
            //将队列的请求消费到一个集合保存
            for (int i = 0; i < size; i++) {
                // 后面的SQL语句是有长度限制的,所以还要做限制每次批量的数量,超过最大任务数,等下次执行
                if (i < MAX_TASK_NUM) {
                    list.add(queue.poll());
                }
            }
            //拿到我们需要去数据库查询的特征,保存为集合
            List<Request> userReqs = new ArrayList<>();
            for (Request request : list) {
                userReqs.add(request);
            }
            //将参数传入service处理, 这里是本地服务,也可以把userService 看成RPC之类的远程调用
            Map<String, Users> response = userService.queryUserByIdBatch(userReqs);
            //将处理结果返回各自的请求
            for (Request request : list) {
                Users result = response.get(request.requestId);
                request.completableFuture.complete(result);    //completableFuture.complete方法完成赋值,这一步执行完毕,下面future.get()阻塞的请求可以继续执行了
            }
        }, 100, 10, TimeUnit.MILLISECONDS);
        //scheduleAtFixedRate是周期性执行 schedule是延迟执行 initialDelay是初始延迟 period是周期间隔 后面是单位
        //这里我写的是 初始化后100毫秒后执行,周期性执行10毫秒执行一次
    }

    public Users queryUser(Long userId) {
        Request request = new Request();
        // 这里用UUID做请求id
        request.requestId = UUID.randomUUID().toString().replace("-", "");
        request.userId = userId;
        CompletableFuture<Users> future = new CompletableFuture<>();
        request.completableFuture = future;
        //将对象传入队列
        queue.offer(request);
        //如果这时候没完成赋值,那么就会阻塞,直到能够拿到值
        try {
            return future.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        return null;
    }
}

控制层调用

/***
 * 请求合并
 * */
@RequestMapping("/merge")
public Callable<Users> merge(Long userId) {
    return new Callable<Users>() {
        @Override
        public Users call() throws Exception {
            return userBatchService.queryUser(userId);
        }
    };
}

Callable是什么可以参考:

https://blog.youkuaiyun.com/baidu_19473529/article/details/123596792

模拟高并发查询的代码

package com.springboot.sample;

import org.springframework.web.client.RestTemplate;

import java.util.Random;
import java.util.concurrent.CountDownLatch;

public class TestBatch {
    private static int threadCount = 30;

    private final static CountDownLatch COUNT_DOWN_LATCH = new CountDownLatch(threadCount); //为保证30个线程同时并发运行

    private static final RestTemplate restTemplate = new RestTemplate();

    public static void main(String[] args) {


        for (int i = 0; i < threadCount; i++) {//循环开30个线程
            new Thread(new Runnable() {
                public void run() {
                    COUNT_DOWN_LATCH.countDown();//每次减一
                    try {
                        COUNT_DOWN_LATCH.await(); //此处等待状态,为了让30个线程同时进行
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    for (int j = 1; j <= 3; j++) {
                        int param = new Random().nextInt(4);
                        if (param <=0){
                            param++;
                        }
                        String responseBody = restTemplate.getForObject("http://localhost:8080/asyncAndMerge/merge?userId=" + param, String.class);
                        System.out.println(Thread.currentThread().getName() + "参数 " + param + " 返回值 " + responseBody);
                    }
                }
            }).start();

        }
    }
}

测试效果

图片

图片

要注意的问题

  • Java 8 的 CompletableFuture 并没有 timeout 机制

  • 后面的SQL语句是有长度限制的,所以还要做限制每次批量的数量,超过最大任务数,等下次执行(本例中加了MAX_TASK_NUM判断)

使用队列的超时解决Java 8 的 CompletableFuture 并没有 timeout 机制

核心代码

package com.springboot.sample.service.impl;

import com.springboot.sample.bean.Users;
import com.springboot.sample.service.UserService;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.*;

/***
 * zzq
 * 包装成批量执行的地方,使用queue解决超时问题
 * */
@Service
public class UserWrapBatchQueueService {
    @Resource
    private UserService userService;

    /**
     * 最大任务数
     **/
    public static int MAX_TASK_NUM = 100;


    /**
     * 请求类,code为查询的共同特征,例如查询商品,通过不同id的来区分
     * CompletableFuture将处理结果返回
     */
    public class Request {
        // 请求id
        String requestId;

        // 参数
        Long userId;
        // 队列,这个有超时机制
        LinkedBlockingQueue<Users> usersQueue;


        public String getRequestId() {
            return requestId;
        }

        public void setRequestId(String requestId) {
            this.requestId = requestId;
        }

        public Long getUserId() {
            return userId;
        }

        public void setUserId(Long userId) {
            this.userId = userId;
        }

        public LinkedBlockingQueue<Users> getUsersQueue() {
            return usersQueue;
        }

        public void setUsersQueue(LinkedBlockingQueue<Users> usersQueue) {
            this.usersQueue = usersQueue;
        }
    }

    /*
    LinkedBlockingQueue是一个阻塞的队列,内部采用链表的结果,通过两个ReenTrantLock来保证线程安全
    LinkedBlockingQueue与ArrayBlockingQueue的区别
    ArrayBlockingQueue默认指定了长度,而LinkedBlockingQueue的默认长度是Integer.MAX_VALUE,也就是无界队列,在移除的速度小于添加的速度时,容易造成OOM。
    ArrayBlockingQueue的存储容器是数组,而LinkedBlockingQueue是存储容器是链表
    两者的实现队列添加或移除的锁不一样,ArrayBlockingQueue实现的队列中的锁是没有分离的,即添加操作和移除操作采用的同一个ReenterLock锁,
    而LinkedBlockingQueue实现的队列中的锁是分离的,其添加采用的是putLock,移除采用的则是takeLock,这样能大大提高队列的吞吐量,
    也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。
     */
    private final Queue<Request> queue = new LinkedBlockingQueue();

    @PostConstruct
    public void init() {
        //定时任务线程池,创建一个支持定时、周期性或延时任务的限定线程数目(这里传入的是1)的线程池
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);

        scheduledExecutorService.scheduleAtFixedRate(() -> {
            int size = queue.size();
            //如果队列没数据,表示这段时间没有请求,直接返回
            if (size == 0) {
                return;
            }
            List<Request> list = new ArrayList<>();
            System.out.println("合并了 [" + size + "] 个请求");
            //将队列的请求消费到一个集合保存
            for (int i = 0; i < size; i++) {
                // 后面的SQL语句是有长度限制的,所以还要做限制每次批量的数量,超过最大任务数,等下次执行
                if (i < MAX_TASK_NUM) {
                    list.add(queue.poll());
                }
            }
            //拿到我们需要去数据库查询的特征,保存为集合
            List<Request> userReqs = new ArrayList<>();
            for (Request request : list) {
                userReqs.add(request);
            }
            //将参数传入service处理, 这里是本地服务,也可以把userService 看成RPC之类的远程调用
            Map<String, Users> response = userService.queryUserByIdBatchQueue(userReqs);
            for (Request userReq : userReqs) {
                // 这里再把结果放到队列里
                Users users = response.get(userReq.getRequestId());
                userReq.usersQueue.offer(users);
            }

        }, 100, 10, TimeUnit.MILLISECONDS);
        //scheduleAtFixedRate是周期性执行 schedule是延迟执行 initialDelay是初始延迟 period是周期间隔 后面是单位
        //这里我写的是 初始化后100毫秒后执行,周期性执行10毫秒执行一次
    }

    public Users queryUser(Long userId) {
        Request request = new Request();
        // 这里用UUID做请求id
        request.requestId = UUID.randomUUID().toString().replace("-", "");
        request.userId = userId;
        LinkedBlockingQueue<Users> usersQueue = new LinkedBlockingQueue<>();
        request.usersQueue = usersQueue;
        //将对象传入队列
        queue.offer(request);
        //取出元素时,如果队列为空,给定阻塞多少毫秒再队列取值,这里是3秒
        try {
            return usersQueue.poll(3000,TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return null;
    }
}
...省略..

    @Override
    public Map<String, Users> queryUserByIdBatchQueue(List<UserWrapBatchQueueService.Request> userReqs) {
        // 全部参数
        List<Long> userIds = userReqs.stream().map(UserWrapBatchQueueService.Request::getUserId).collect(Collectors.toList());
        QueryWrapper<Users> queryWrapper = new QueryWrapper<>();
        // 用in语句合并成一条SQL,避免多次请求数据库的IO
        queryWrapper.in("id", userIds);
        List<Users> users = usersMapper.selectList(queryWrapper);
        Map<Long, List<Users>> userGroup = users.stream().collect(Collectors.groupingBy(Users::getId));
        HashMap<String, Users> result = new HashMap<>();
        // 数据分组
        userReqs.forEach(val -> {
            List<Users> usersList = userGroup.get(val.getUserId());
            if (!CollectionUtils.isEmpty(usersList)) {
                result.put(val.getRequestId(), usersList.get(0));
            } else {
                // 表示没数据 , 这里要new,不然加入队列会空指针
                result.put(val.getRequestId(), new Users());
            }
        });
        return result;
    }

...省略...

小结

请求合并,批量的办法能大幅节省被调用系统的连接资源,本例是以数据库为例,其他RPC调用也是类似的道理。缺点就是请求的时间在执行实际的逻辑之前增加了等待时间,不适合低并发的场景。

源码:https://gitee.com/apple_1030907690/spring-boot-kubernetes/tree/v1.0.5

一文掌握:Gitlab的完整使用手册 GitLab是一个强大的版本控制和协作平台,用于管理代码仓库、项目、问题跟踪、持续集成和部署等软件开发任务。可以做一些创建项目、添加成员、管理代码、问题跟踪和持续集成等方面功能。 阅读详情

相关推荐

一文让你认识 axios

安装axiosnpminstallaxios--save bowerinstallaxios--save 引用直接script标签引用<scriptsrc="...

人生代码 ---- 公众号 683

接口请求合并技巧,用好了效率直接翻倍!

一、什么是请求合并在WEB项目中,我们一般会使用HTTP协议来处理请求那么我们与服务器交互方式将会是这样的,一次请求,一次处理我们都知道,调用批量接口相比调用非批量接口有更大的性能优势(因为减少了IO交互操作),在高并发情况下,如果有非常频繁的接口请求发生的话,我们则可以考虑请求合并了,将多个请求进行一定的等待延迟,当请求累计达到一定量级的时候,进行批量请求处理二、请求合并的优缺点所谓请求合并,就...

Java知音 1611

从ARCore到AICore:揭秘Google如何通过系统级服务推动Android生态创新

本文深入探讨了Google如何通过AICore等系统级服务推动Android生态创新。从ARCore到AICore,Google通过分层解耦策略将AI能力下沉至系统层,实现端侧大模型推理,如Gemini Nano在Pixel 8 Pro中的应用。文章剖析了AICore的技术架构、开发者赋能路径及场景化实践,展示了其在通信、内容创作和生产力工具中的卓越表现。

rust6ferris的博客 546

高性能手段之合并请求

请求合并顾名思义就是将客户端产生的多个请求归并到一个一次对下游的请求请求合并本质上是时间换空间(性能)的一个思路,利用时间的消耗来积累请求并实现合并请求提高性能的目的,本身也是一种优化手段。有人会说时间延长时间不是让请求时间处理变长了吗?未必,这要结合业务场景以及业务量来综合考量。时间换空间、空间换时间最终的目的都是提升我们系统性能的手段,没有哪个是绝对好的,我们要综合考虑,结合系统现状看哪个带来的收益大就用哪个手段。

am_danger 2249

GOM Correlate拉伸试验后处理操作

本文描述了使用GOM Software 2022对拉伸试样进行后处理的操作,包括设置检测点、引伸计、输出应变数据到表格、输出报告为视频等

qq_45008924的博客 4667

SAP 物料账前期没有运行的解决方法

If you use the actual costing (material ledger), you must have completely closed all period-end closing operations in the actual costing for the penultimate period prior to the period shift. Use the '...

含笑饮砒霜死后更嚣张 3319

请求合并

高并发场景中,调用批量接口相比调用非批量接口有更大的性能优势。但有时候,请求更多的是单个接口,不能够直接调用批量接口,如果这个接口是高频接口,对其做请求合并就很有必要了。比如电影网站的获取电影详情接口,APP的一次请求是单个接口调用,用户量少的时候请求也不多,完全没问题;但同一时刻往往有大量用户访问电影详情,是个高并发的高频接口,如果都是单次查询,后台就不一定能hold住了。为了优化这个接口,后台...

javasoftdeveloper的博客 5151

基于 Qt C++ 的 2D 动画制作工具

你可以修改它的中心坐标 (X, Y)、宽度 (W)、高度 (H),或点击 "选择颜色" 按钮改变其填充色。* **图形视图 (`QGraphicsView` 和 `QGraphicsScene`)**: 用于显示和编辑当前帧的内容。

寒寒豆豆软件公司的博客 117

请求合并的三种方式,大大提高接口性能!

程序员的成长之路互联网/程序员/技术/资料共享关注阅读本文大概需要 8 分钟。作者:zhenbianshu来源:https://zhenbianshu.github.io/将相似或重复请...

良月柒 1733

Android14 Qcom 从Framework到Driver带你连接WIFI(一)

前面已经讲完了WIFI打开的流程,那么通过UI这时候是可以选择需要连接的网络进行连接。从这里开始我们看连接的流程。

IAYLBL的博客 696

YOLOv11模型改进全攻略:利用轻量化模块实现精度与速度双跃升

如果你在实操中遇到模块替换导致的精度骤降或部署报错,不妨尝试调整轻量化模块的替换比例,或针对性地保留关键层的原始结构。记住,轻量化的核心是“在精度损失可接受的范围内最大化提升效率”,只要结合业务场景灵活取舍,你也能让YOLOv11在资源受限的环境中大放异彩。接下来,我们从模块选型到代码集成,一步步带你打造“轻量且能打”的YOLOv11,真正帮你解决工业落地中的效率与精度痛点。训练过程中关注模型的mAP和推理速度,若精度下降过多,可调整模块替换的比例(如仅替换Backbone的部分卷积层)。

m0_64661770的博客 502

请求合并有什么意义?一文详解【送源码】

请求合并到底有什么意义呢?我们来看下图。图片假设我们3个用户(用户id分别是1、2、3),现在他们都要查询自己的基本信息,请求到服务器,服务器端请求数据库,发出3次请求。我们都知道数据库连接资源是相当宝贵的,那么我们怎么尽可能节省连接资源呢?这里把数据库换成被调用的远程服务,也是同样的道理。我们改变下思路,如下图所示。图片我们在服务器端把请求合并,只发出一条SQL查询数据库,数据库返回后,服务器端处理返回数据,根据一个唯一请求ID,把数据分组,返回给对应用户。

java_121388的博客 658

Altium Designer 超详细入门教程

本文是一份Altium Designer新手入门教程,详细介绍了从软件基础到PCB制版的全流程。主要内容包括:软件界面介绍与工程创建方法;原理图设计中的元件放置、电气连线、标注添加等操作;PCB设计的布局布线、铺铜和DRC检查;Gerber和钻孔文件的导出方法。教程还提供了分类整理的实用快捷键表和新手避坑指南,强调原理图编译和DRC检查的重要性。通过本教程,新手可以快速掌握AD基本操作,完成简单电路板的设计与制版文件导出。

学习中...... 846

请求合并java场景_SpringCloud (九) Hystrix请求合并的使用

前言:承接上一篇文章,两文本来可以一起写的,但是发现RestTemplate使用普通的调用返回包装类型会出现一些问题,也正是这个问题,两文没有合成一文,本文篇幅不会太长,会说一下使用和适应的场景。本文简单记述了Hystrix的请求合并的使用本文内容:一、请求合并是做什么的?如图,多个客户端发送请求调用RibbonConsumHystrix(消费者)项目中的findOne方法,这时候在这个项目中的线...

weixin_35753291的博客 347

在windows安装程序时出现 系统无法打开指定的设备或文件的解决方案

本文主要介绍了在windows安装程序时出现 系统无法打开指定的设备或文件的解决方案,希望能对使用windows的同学们有所帮助。 文章目录 1. 问题描述 2. 解决方案

weixin_43178406的博客 1万+

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

SingleFlight 深度解析:从原理到源码,一文读懂请求合并利器

SingleFlight 是一种重复函数调用抑制机制,它将相同 key 的并发请求合并为一次实际调用,所有请求共享该调用的结果。SingleFlight 是一个小而美核心价值:将 N 个并发请求合并为 1 次实际调用,大幅降低下游压力实现精髓的三重奏,简洁而高效典型场景:缓存击穿防护、接口防重、分布式锁优化。

weixin_52056853的博客 426

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

git全面详解,一文了解全程

HEAD 表示当前所在的分支节点git rebase 将当前分支提交到branch分支上Git checkout ^ 切换到当前分支的最近一次提交的父提交git checkout ~n 切换到当前分支的最近n次提交的父提交Git branch -f 将1分支强行指到2分支Git reset 回退到某个分支Git revert 将上述分支的之前提交作为新提交。

m0_70597433的博客 2406

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

SpringCloud (九) Hystrix请求合并的使用

前言: 承接上一篇文章,两文本来可以一起写的,但是发现RestTemplate使用普通的调用返回包装类型会出现一些问题,也正是这个问题,两文没有合成一文,本文篇幅不会太长,会说一下使用和适应的场景。 本文简单记述了Hystrix的请求合并的使用 >注意:本文项目地址:https://github.com/HellxZ/SpringCloudLearn.git 目录: 前言 目录 本文...

weixin_30593261的博客 140

JMeter HTTP 请求头解析,一文搞懂

下方这份完整的软件测试视频教程已经整理上传完成,需要的朋友们可以自行领取【保证100%免费】​​​。

Hacker_Oldv的博客 1352

你真的懂PHP空合并赋值吗?一文看懂数组场景下的陷阱与优化

掌握PHP 7.3空合并赋值的数组用法,避免常见陷阱并提升代码健壮性。详解适用场景、语法优势与潜在问题,教你正确处理数组键缺失情况,优化变量赋值逻辑。实用技巧值得收藏。

DeepLens的博客 928

一文搞懂前端请求超时与取消:从 Promise.race 到 AbortController

本文介绍了两种处理请求超时和取消的方法:Promise.race和AbortController。Promise.race只能实现超时提示,无法真正取消请求,而AbortController可以同时解决超时和用户主动取消的问题。文章提供了最小可用示例和封装方法,并对比了axios的用法。最后总结了常见踩坑点,强调要区分AbortError和HttpError。核心结论是:要真正取消请求就使用AbortController,Promise.race只是计时器功能。

2604_95456017的博客 537
上一篇: 大量简化代码!Java Stream 简单技巧!
下一篇: 简单易懂!图解6种微服务架构的设计模式
架构文摘JGWZ
博客等级 码龄5年 8808粉丝 345原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值