并发请求指的是在同一段时间内,永远有一个或多个请求被发送出去的情况。并不是指这些请求完全同时发送,而是一段时间内,这些请求可能因为时间的微小差异而先后被发送。这些请求可能彼此相互独立,也可能存在相互依赖的关系。
并发请求的一个典型应用场景是网页加载
。当你访问一个网页时,浏览器会同时向服务器发送多个请求,来获取页面的HTML文档、样式表、JavaScript脚本、图片等多种资源。这些请求基本上是同时发出的,但由于网络延迟和服务器处理速度的差异,这些请求可能会在不同的时间点得到响应。
在处理并发请求时,我们通常要考虑以下问题:
请求的顺序性:如果一个请求的处理结果依赖于另一个请求的结果,那么我们就需要确保这两个请求的执行顺序。
请求的并发数:如果有太多的请求同时发送,可能会对服务器造成负担,也可能会受到浏览器或服务器对并发连接数的限制。
在JavaScript中,我们可以使用Promise
、async/await
等方式来管理和控制并发请求。对于超出限制的并发请求,我们还可以使用一些第三方库
,如axios、request-promise等来进行队列化处理或并发控制。
在JavaScript中,有很多不同的方法可以用来处理并发请求:
这些请求会并发地执行,而Promise.all()会返回一个新的Promise对象,只有当所有请求都成功执行并返回响应后,这个新的Promise才会变为resolved状态
。例如:
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));
例如:
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);
})();
Promise.race()在任一Promise变为fulfilled或reject时结束,Promise.any()则会在任一Promise达到fulfilled状态时结束
。在处理并发请求时,如果需要保证请求的顺序性,则通常意味着请求之间存在依赖关系
,可能需要先发送某个请求,并等待接收和处理其响应之后,再发送下一个请求。
在JavaScript中,有多种方式可以实现这种顺序执行的效果。
.then()
的回调函数中返回一个新的Promise,就可以形成一个Promise链,从而使多个操作能够按照指定的顺序执行。例如: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请求成功并处理完毕后发出。
async/await是基于Promise的语法糖
,使异步操作看起来更像是同步操作
,以此可以很容易地实现请求的顺序性。例如: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
。然而,当我们构建后端应用程序,或者需要控制并发请求数量时
以下是一种常见的控制并发请求并发数的方法:
我们可以使用队列的方法来控制并发数量。可以创建一个最大并发数固定的请求队列,当一个请求完成时,再从队列里取出下一个请求,这样就可以控制并发数不超过设定的最大并发数。
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.js
,bottleneck
,p-limit
等