前端请求乱序问题分析与AbortController、async/await、Promise.all等解决方案

在这里插入图片描述

🌷 古之立大事者,不惟有超世之才,亦必有坚忍不拔之志
🎐 个人CSND主页——Micro麦可乐的博客
🐥《Docker实操教程》专栏以最新的Centos版本为基础进行Docker实操教程,入门到实战
🌺《RabbitMQ》专栏主要介绍使用JAVA开发RabbitMQ的系列教程,从基础知识到项目实战
🌸《设计模式》专栏以实际的生活场景为案例进行讲解,让大家对设计模式有一个更清晰的理解
💕《Jenkins实战》专栏主要介绍Jenkins+Docker的实战教程,让你快速掌握项目CI/CD,是2024年最新的实战教程
🌞《Spring Boot》专栏主要介绍我们日常工作项目中经常应用到的功能以及技巧,代码样例完整
如果文章能够给大家带来一定的帮助!欢迎关注、评论互动~

前言

在我们日常前端开发过程中,常常需要发起多个异步请求去获取数据。然而,当多个请求并发执行时,可能因网络延迟、服务器响应速度差异等问题导致响应顺序与发送顺序不一致,这种现象称为请求乱序

这种问题在分页加载、实时搜索、连续数据提交等场景中尤为突出,可能导致数据错乱、状态不一致等严重问题。

接下来博主针对上述这些问题,和大家分享一下目前常见的几种解决方案

分析具体原因

1、异步请求的并发执行

前端请求(例如,使用 fetchaxiosXMLHttpRequest)是基于异步操作的。这意味着当发起请求时,JavaScript 引擎不会等待请求的结果返回,而是立即继续执行后续代码。因此,当我们发起多个请求时,这些请求是并发执行的,它们的返回顺序是不确定的,可能与请求发出的顺序不同。

2、网络延迟

除了异步请求并发执行本身,网络延迟也是导致请求乱序的因素之一。即使某些请求先发出,由于网络环境的不同,某些请求可能会因为服务器响应时间较长或者网络不稳定而比其他请求更晚返回,从而导致结果乱序。

开发过程可能遇到到的影响

  • 分页内容与页码不匹配
  • 界面显示过期数据
  • 表单提交顺序错乱
  • 实时聊天信息顺序颠倒

常见的几种解决方案

下面博主将针对不同的应用场景,来分别列举解决方案。总体分为两个大类:
在这里插入图片描述
其次不终止请求,又对应N种可以解决的方案

❶ 请求中断(AbortController)

请求中断法相对实用于 搜索 / 分页 场景 ,模拟场景如下

A、在我们搜索框第一次输入关键字进行搜索,由于网络问题导致一直没有返回结果,此时我们又输入了其它关键字搜索,但是第一次请求是最后返回,就会出现第二次关键词返回了第一次关键词的搜索结果

B、同理分页列表用户快速点击各页码进行获取分页数据,由于种种问题可能也会导致你页码数据不正确

let controller = null;

async function search(keyword) {
  // 终止前一个未完成的请求
  if (controller) controller.abort();
  controller = new AbortController();
  
  try {
    const res = await fetch(`/api/search?q=${keyword}`, {
      signal: controller.signal
    });
    const results = await res.json();
    showResults(results);
  } catch (err) {
    if (err.name !== 'AbortError') {
      handleError(err);
    }
  }
}

❷ 序列号验证

适用场景:实时数据更新、聊天应用等需要时序保证的场景

let latestRequestId = 0;

async function fetchData() {
  const currentId = ++latestRequestId;
  
  const res = await fetch('/api/current-data');
  const data = await res.json();
  
  // 仅处理最新请求的响应
  if (currentId === latestRequestId) {
    updateUI(data);
  }
}

实现要点:

  • 为每个请求生成唯一递增ID
  • 响应处理时验证ID有效性
  • 丢弃过期请求的响应

❸ 请求队列

适用场景:需要严格保证顺序的连续操作(如多步表单提交)

class RequestQueue {
  constructor() {
    this.pending = Promise.resolve();
  }

  add(requestFn) {
    this.pending = this.pending.then(
      () => requestFn(),
      () => requestFn() // 即使前序失败也继续执行
    );
    return this.pending;
  }
}

// 使用示例
const queue = new RequestQueue();

function safeUpdate(data) {
  return queue.add(() => 
    fetch('/api/update', {
      method: 'POST',
      body: JSON.stringify(data)
    })
  );
}

队列特性:

  • 先进先出(FIFO)保证
  • 自动串行化处理
  • 错误隔离机制

❹ 版本标记

适用场景:需要快速响应且允许临时状态不一致的场景

let dataVersion = 0;

async function updateItem(item) {
  const currentVersion = ++dataVersion;
  
  // 乐观更新
  ui.setOptimisticData(item);
  
  try {
    const res = await fetch(`/api/items/${item.id}`, {
      method: 'PUT',
      body: JSON.stringify(item)
    });
    
    const newData = await res.json();
    
    // 仅当是最新操作才更新
    if (currentVersion === dataVersion) {
      ui.applyConfirmedData(newData);
    }
  } catch (err) {
    ui.rollbackData(item.id);
  }
}

❺ 使用 async/await

async/awaitJavaScript 中处理异步操作的语法糖,它使得异步代码看起来像同步代码。通过在异步请求之间使用 await,可以确保请求按顺序执行。

async function fetchDataSequentially() {
  try {
    const response1 = await fetch('/api/items/data1');
    const data1 = await response1.json();
    console.log('Data 1:', data1);

    const response2 = await fetch('/api/items/data2');
    const data2 = await response2.json();
    console.log('Data 2:', data2);

    const response3 = await fetch('/api/items/data3');
    const data3 = await response3.json();
    console.log('Data 3:', data3);
  } catch (error) {
    console.error('Error fetching data:', error);
  }
}

fetchDataSequentially();

在上述代码中,使用 ·await· 确保了每个请求依赖于前一个请求的返回,确保请求按顺序执行。

❻ 使用 Promise.all

如果不需要保证请求的执行顺序,而是希望同时发起多个请求并在所有请求完成后再处理结果,可以使用 Promise.all。它将多个 Promise 对象合并为一个新的 Promise,只有所有请求都完成时才会返回结果

async function fetchDataConcurrently() {
  try {
    const [response1, response2, response3] = await Promise.all([
      fetch('/api/items//data1'),
      fetch('/api/items//data2'),
      fetch('/api/items//data3'),
    ]);

    const data1 = await response1.json();
    const data2 = await response2.json();
    const data3 = await response3.json();

    console.log('Data 1:', data1);
    console.log('Data 2:', data2);
    console.log('Data 3:', data3);
  } catch (error) {
    console.error('Error fetching data:', error);
  }
}

fetchDataConcurrently();

上述代码中,所有请求并行执行,Promise.all 会等待所有请求完成后再处理结果。需要注意的是,Promise.all 返回的结果数组的顺序与 Promise.all 传入的顺序一致,因此在后续处理时,数据顺序可以得到保证。

总结

请求顺序管理需要根据具体业务场景进行权衡取舍。相信通过博主的讲解,小伙伴们已经了解并能解决请求乱序问题,这样就能够帮助我们更好地处理依赖关系,提升用户体验,并确保业务逻辑的正确性。
如果本文对您有所帮助,希望 一键三连 给博主一点点鼓励,如果您有任何疑问或建议,请随时留言讨论!


在这里插入图片描述

评论 38
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Micro麦可乐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值