支线任务遇到的怪——并发请求

并发请求指的是在同一段时间内,永远有一个或多个请求被发送出去的情况。并不是指这些请求完全同时发送,而是一段时间内,这些请求可能因为时间的微小差异而先后被发送。这些请求可能彼此相互独立,也可能存在相互依赖的关系

并发请求的一个典型应用场景是网页加载。当你访问一个网页时,浏览器会同时向服务器发送多个请求,来获取页面的HTML文档、样式表、JavaScript脚本、图片等多种资源。这些请求基本上是同时发出的,但由于网络延迟和服务器处理速度的差异,这些请求可能会在不同的时间点得到响应。

在处理并发请求时,我们通常要考虑以下问题:

  • 请求的顺序性:如果一个请求的处理结果依赖于另一个请求的结果,那么我们就需要确保这两个请求的执行顺序。

  • 请求的并发数:如果有太多的请求同时发送,可能会对服务器造成负担,也可能会受到浏览器或服务器对并发连接数的限制。

在JavaScript中,我们可以使用Promiseasync/await等方式来管理和控制并发请求。对于超出限制的并发请求,我们还可以使用一些第三方库,如axios、request-promise等来进行队列化处理或并发控制。

在JavaScript中,有很多不同的方法可以用来处理并发请求:

  1. Promise.all():这是解决并行请求处理的一个常用方法。可以将一个需要并发执行的请求数组作为参数传递给Promise.all()方法,这些请求会并发地执行,而Promise.all()会返回一个新的Promise对象,只有当所有请求都成功执行并返回响应后,这个新的Promise才会变为resolved状态

例如:

javascript 复制代码
let request1 = fetch('https://api.myjson.com/bins/9hog6');
let request2 = fetch('https://api.myjson.com/bins/nttn4');

Promise.all([request1, request2])
.then(responses => {
  return Promise.all(responses.map(response => response.json()))
})
.then(data => console.log(data))
.catch(err => console.log(err));
  1. Async/Await:也可以使用async函数和await表达式来处理并发请求。在async函数内部,你可以使用await来暂停函数执行,直到Promise被resolved或rejected为止。

例如:

javascript 复制代码
async function fetchAndDecode(url) {
  let response = await fetch(url);
  let data = await response.json();
  return data;
}

(async function() {
  let data1 = await fetchAndDecode('https://api.myjson.com/bins/wx4io');
  let data2 = await fetchAndDecode('https://api.myjson.com/bins/jv265');
  console.log(data1, data2);
})();
  1. Promise.any()/Promise.race():这两个方法都会在任何一个Promise达到resolved状态时结束,但有区别,Promise.race()在任一Promise变为fulfilled或reject时结束,Promise.any()则会在任一Promise达到fulfilled状态时结束

怎么去保证并发请求的顺序性?

在处理并发请求时,如果需要保证请求的顺序性,则通常意味着请求之间存在依赖关系,可能需要先发送某个请求,并等待接收和处理其响应之后,再发送下一个请求。

在JavaScript中,有多种方式可以实现这种顺序执行的效果。

  1. 使用Promise的链式调用:通过在.then()的回调函数中返回一个新的Promise,就可以形成一个Promise链,从而使多个操作能够按照指定的顺序执行。例如:
javascript 复制代码
fetch('https://api.example.com/data1')
  .then(response => response.json())
  .then(data1 => {
    console.log(data1);
    return fetch('https://api.example.com/data2');
  })
  .then(response => response.json())
  .then(data2 => console.log(data2));

在上面的代码中,第二个fetch请求会在第一个fetch请求成功并处理完毕后发出。

  1. 使用async/await:async/await是基于Promise的语法糖使异步操作看起来更像是同步操作,以此可以很容易地实现请求的顺序性。例如:
javascript 复制代码
async function fetchData() {
  let response1 = await fetch('https://api.example.com/data1');
  let data1 = await response1.json();
  console.log(data1);
  
  let response2 = await fetch('https://api.example.com/data2');
  let data2 = await response2.json();
  console.log(data2);
}

fetchData();

在上面的代码中,使用了async/await确保第二个fetch请求会在第一个fetch请求成功并处理完毕后发出。

注意 :以上二种方法都有一个共同的特点,那就是后一个请求必须等待前一个请求完全结束后才能开始。如果你的请求之间没有依赖关系,那么这种做法可能会降低程序的性能,因为不能充分利用网络的并发性。在没有依赖关系的情况下,更好的做法是同时发出这些请求,然后在所有请求都结束后再进行后续处理(可以使用Promise.all()

怎么控制请求的并发数?

在 JavaScript 中,并发请求的并发数是由浏览器自己决定的 ,这一数值通常是固定的,通常为6-8。然而,当我们构建后端应用程序,或者需要控制并发请求数量时

以下是一种常见的控制并发请求并发数的方法:

我们可以使用队列的方法来控制并发数量。可以创建一个最大并发数固定的请求队列,当一个请求完成时,再从队列里取出下一个请求,这样就可以控制并发数不超过设定的最大并发数。

javascript 复制代码
let maxConcurrentRequests = 5; // 设定最大并发数
let currentRequests = 0; // 当前并发数
let requestQueue = []; // 请求队列

function request(url){
  //如果当前并发请求数(`currentRequests`)小于最大并发数(`maxConcurrentRequests`),
  //函数内部会调用fetch来发送一个请求,并把当前并发数加一。Fetch返回的是一个Promise,请求完
  //成后会执行`then()`里面的内容,把当前请求数减一,然后检查请求队列(`requestQueue`),
  //如果队列中还有请求,取出队列中的第一个请求并调用`request`函数发送
  // 如果当前并发请求数达到了最大并发数,那么新的请求
  //就不能被立即执行,于是把新的请求放入请求队列中等待
  if(currentRequests < maxConcurrentRequests){
    currentRequests++;
    fetch(url).then(() => {
      currentRequests--; // 请求结束,减少并发数
      if (requestQueue.length > 0) { 
        // 如果队列中还有等待的请求,就取出一个继续发送
        request(requestQueue.shift()); 
      }
    });  
  } else {
    requestQueue.push(url);
  }
}

// 以下是对以上函数的使用方式,模拟发送并发请求
for(let i=0; i<15; i++){
  request('https://someurl.com/api');
}

当并发数超过指定的阈值时,新的请求会被放置在队列中,待一个请求完成将请求结束返回之后再从队列中取出新的请求执行。这样,任何时候实际发出的请求数量都不会超过我们预设的上限。

很多第三方的包/库提供了并发控制的功能,如 async.jsbottleneckp-limit

相关推荐
东风破_1 小时前
Docker 基础:为什么需要容器?
前端·后端·nginx
东风破_1 小时前
Docker 实战:使用 Nginx 作为容器入口代理 Node 服务
前端·后端·nginx
剩下了什么2 小时前
float32 与 float64 精度陷阱:如何在 Go 中避免错误使用
开发语言·后端·golang
你别说话了2 小时前
Vue项目解决跨域
前端·javascript·vue.js
指尖时光.2 小时前
Three.js 超全入门综合案例
开发语言·javascript·ecmascript
唐青枫2 小时前
http-server:别再双击 index.html,一条命令把目录变成网站
前端·javascript
暴力求解2 小时前
Linux网络---传输层协议TCP(二)
linux·服务器·开发语言·网络·tcp/ip
吴声子夜歌3 小时前
Java面试题——基础(一)
java·开发语言
网安蟹佬霸3 小时前
OSINT开源情报收集实战:从信息搜集到资产测绘(2026最新万字保姆级指南)
前端·网络·安全·web安全·网络安全·开源
小庞在加油3 小时前
WinDbg实战:QT/跨平台项目死锁与无响应问题排查指南
开发语言·qt·windbg·工具