Skip to Content
Nextra 4.0 is released 🎉
笔记Web使用 axios 实现前端并发限制和重试机制

使用 axios 实现前端并发限制和重试机制

在 Web 开发中, 我们经常需要向后端发送多个异步请求以获取数据, 然而过多的请求可能会对服务器造成过大的压力, 影响系统的性能。因此, 我们需要对并发请求进行限制。同时, 由于网络环境的不稳定性, 发送请求时也需要考虑添加重试机制, 以提高请求的成功率和可靠性。

本篇博客将介绍如何使用 axios 实现前端并发限制和重试机制。axios 是一款基于 PromiseHTTP 客户端, 可以用于浏览器和 NodeJs 环境下发送 HTTP 请求。

前端并发限制的实现

我们可以使用一个请求队列来限制并发请求的数量, 每次发送请求时, 将请求加入到队列中, 并检查当前队列的长度是否小于最大并发请求数量, 如果是, 就从队列中取出一个请求并发送;如果不是, 就等待前面的请求完成后再发送下一个请求。

以下是一个使用 axios 实现前端并发限制的示例代码:

const axios = require('axios'); // 最大并发请求数 const MAX_CONCURRENT_REQUESTS = 5; // 请求队列 const requestQueue = []; // 当前正在进行中的请求数 let activeRequests = 0; /** * 处理请求队列中的请求 */ function processQueue() { // 如果当前进行中的请求数没达到最大并发数 && 请求队列不为空 if (activeRequests < MAX_CONCURRENT_REQUESTS && requestQueue.length > 0) { // 从请求队列中取出队头的请求 const { url, config, resolve, reject } = requestQueue.shift(); // 进行中的请求数 +1 activeRequests++; // 通过 Axios 发送请求 axios(url, config) .then((response) => { // 请求成功, 将 外层 Promise 的状态更新为 fulfilled, 并返回请求结果 resolve(response); }) .catch((error) => { // 请求失败, 将 外层 Promise 的状态更新为 rejected, 并返回错误信息 reject(error); }) .finally(() => { // 不论成功还是失败都会执行 finally, 表示本次请求结束, 将进行中的请求数 -1 activeRequests--; // 再处理请求队列中的下一个请求 processQueue(); }); } } /** * 并发请求方法 * @param { string } url 请求的 url * @param { AxiosRequestConfig } config Axios 的请求配置 */ function limitConcurrentRequests(url, config) { // 这里很关键, 将用户发起的每个请求都变成一个 Promise, 而 Promise 的状态会在 processQueue 中根据 Axios 的执行结果来更新 return new Promise((resolve, reject) => { // 将请求的配置信息 和 更新 Promise 状态的两个方法变成一个对象推入请求队列中 requestQueue.push({ url, config, resolve, reject }); // 执行 processQueue 方法处理请求队列中的每个请求 processQueue(); }); } module.exports = { limitConcurrentRequests };

在以上代码中, 我们设置了一个 MAX_CONCURRENT_REQUESTS 常量, 表示最大并发请求数量, 同时维护了一个请求队列 requestQueue 和一个变量 activeRequests, 分别用于存储请求队列和正在处理的请求数量。我们通过定义 processQueue 方法来处理请求队列中的请求, 它会检查当前正在处理的请求数量是否小于最大并发请求数量, 并从队列中取出一个请求并发送。在发送请求的过程中, 我们通过 Promise 的 thencatch 方法来处理成功和失败的情况, 并在最后通过 finally 方法将正在处理的请求数量减一, 并再次调用 processQueue 方法, 以处理下一个请求。

使用时, 我们可以通过以下代码来进行测试和梳理上述逻辑:

const { limitConcurrentRequests } = require('./concurrency'); // 定义了 20个 URL const urls = ['https://jsonplaceholder.typicode.com/posts/1', 'https://jsonplaceholder.typicode.com/posts/2', 'https://jsonplaceholder.typicode.com/posts/3', 'https://jsonplaceholder.typicode.com/posts/4', 'https://jsonplaceholder.typicode.com/posts/5', 'https://jsonplaceholder.typicode.com/posts/6', 'https://jsonplaceholder.typicode.com/posts/7', 'https://jsonplaceholder.typicode.com/posts/8', 'https://jsonplaceholder.typicode.com/posts/9', 'https://jsonplaceholder.typicode.com/posts/10', 'https://jsonplaceholder.typicode.com/posts/11', 'https://jsonplaceholder.typicode.com/posts/12', 'https://jsonplaceholder.typicode.com/posts/13', 'https://jsonplaceholder.typicode.com/posts/14', 'https://jsonplaceholder.typicode.com/posts/15', 'https://jsonplaceholder.typicode.com/posts/16', 'https://jsonplaceholder.typicode.com/posts/17', 'https://jsonplaceholder.typicode.com/posts/18', 'https://jsonplaceholder.typicode.com/posts/19', 'https://jsonplaceholder.typicode.com/posts/20']; // 通过循环, 同时发起 20 个请求 urls.forEach(url => limitConcurrentRequests(url) .then(responses => console.log(responses.data)) .catch(error => console.error(error)));

在以上代码中, 我们定义了一个包含 20 个 URL 的数组 urls, 通过循环同时发送 20个请求。最后, 我们通过 thencatch 方法分别处理请求成功和失败的情况, 并打印出结果。

前端重试机制的实现

有时, 由于网络环境的不稳定性, 发送的请求可能会失败, 因此我们需要对请求添加重试机制。在实现重试机制时, 我们需要注意以下几点:

  • 需要限制重试的次数, 避免无限重试;
  • 在重试过程中, 需要添加一定的延迟时间, 以避免过于频繁地发送请求;
  • 重试时需要保证请求的幂等性, 即多次重试的结果应该与单次请求的结果一致。

以下是一个使用 axios 实现前端重试机制的示例代码:

const axios = require('axios'); // 最大重试次数, 避免无限重试 const MAX_RETRY_TIMES = 3; // 重试延时, 避免频繁发送请求 const RETRY_DELAY = 1000; /** * 支持重试机制的请求方法, 整体方案是通过 Promise 包裹 Axios【这点和并发请求 limitConcurrentRequests 思路一样】 + 递归的逻辑来实现 * @param { string } url 请求地址 * @param { AxiosRequestConfig } config Axios 的请求配置 * @param { number } retryTimes 请求最大重试次数 * @returns Promise */ function retryableRequest(url, config, retryTimes = MAX_RETRY_TIMES) { return new Promise((resolve, reject) => { // 通过 Axios 发送请求 axios(url, config) .then((response) => { // 请求成功, 直接将 Promise 状态变为 fulfilled resolve(response); }) .catch((error) => { // 请求失败 if (retryTimes === 0) { // 剩余重试次数为 0, 表示本次请求失败, 将 Promise 状态从 pending 更新为 rejected reject(error); } else { // 还能继续重试, RETRY_DELAY 秒之后, 递归调用 retryableRequest 方法, 重新发送请求 setTimeout(() => { // 递归逻辑, 通过递归来实现重试, 每次递归重试次数 -1;根据下层 retryableRequest 方法的 Promise 结果更新当前 Promise 的状态 retryableRequest(url, config, retryTimes - 1) .then((response) => { // 请求成功, 将 Promise 状态从 pending 更新为 fulfilled resolve(response); }) .catch((error) => { // 请求失败, 表示本次请求失败, 将 Promise 状态从 pending 更新为 rejected reject(error); }); }, RETRY_DELAY); } }); }); } module.exports = { retryableRequest };

在以上代码中, 我们设置了一个 MAX_RETRY_TIMES 常量, 表示最大重试次数, 同时定义了一个 RETRY_DELAY 常量, 表示重试的延迟时间。我们通过定义 retryableRequest 方法来实现重试机制, 它会通过递归调用自身来重试请求, 同时在每次重试前会添加一定的延迟时间以避免频繁发送请求。如果重试次数超过了最大重试次数, 就会抛出错误。

以下是一个使用重试机制的示例代码:

const { retryableRequest } = require('./retry'); retryableRequest('https://jsonplaceholder.typicode.com/posts/1', { method: 'get' }) .then((response) => { console.log(response.data); }) .catch((error) => { console.error(error); });

在以上代码中, 我们使用了 retryableRequest 方法来发送请求, 并在 thencatch 方法中处理请求成功和失败的情况。如果请求失败, 重试机制会自动尝试重新发送请求, 直到达到最大重试次数或请求成功为止。

并发限制 + 请求重试

上面分别讲述了 前端并发限制前端重试机制 的实现, 但两者逻辑独立, 接下来会将两者结合, 整体思路是在 并发限制 的基础上增加 请求重试。

const axios = require('axios'); // 最大并发请求数 const MAX_CONCURRENT_REQUESTS = 5; // 请求队列 const requestQueue = []; // 当前正在进行中的请求数 let activeRequests = 0; /** * 处理请求队列中的请求 */ function processQueue() { // 如果当前进行中的请求数没达到最大并发数 && 请求队列不为空 if (activeRequests < MAX_CONCURRENT_REQUESTS && requestQueue.length > 0) { // 从请求队列中取出队头的请求 const { url, config, retryTimes, retryDelay, resolve, reject } = requestQueue.shift(); // 进行中的请求数 +1 activeRequests++; // 通过 Axios 发送请求 axios(url, config) .then((response) => { // 请求成功, 将 外层 Promise 的状态更新为 fulfilled, 并返回请求结果 resolve(response); }) .catch((error) => { // 请求失败 if (retryTimes === 0) { // 剩余重试次数为 0, 表示本次请求失败, 将 外层 Promise 的状态更新为 rejected, 并返回错误信息 reject(error); } else { // 还能继续重试, 将请求重新入队 setTimeout(() => { requestQueue.push({ url, config, retryTimes: retryTimes - 1, retryDelay, resolve, reject }); }, retryDelay); } }) .finally(() => { // 不论成功还是失败都会执行 finally, 表示本次请求结束, 将进行中的请求数 -1 activeRequests--; // 再处理请求队列中的下一个请求 processQueue(); }); } } /** * 请求方法 * @param { string } url 请求的 url * @param { AxiosRequestConfig } config Axios 的请求配置 * @param { number } retryTimes 最大重试次数, 避免无限重试 * @param { number } retryDelay 试延时, 避免频繁发送请求 */ function request(url, config, retryTimes = 3, retryDelay = 1000) { // 这里很关键, 将用户发起的每个请求都变成一个 Promise, 而 Promise 的状态会在 processQueue 中根据 Axios 的执行结果来更新 return new Promise((resolve, reject) => { // 将请求的配置信息 和 更新 Promise 状态的两个方法变成一个对象推入请求队列中 requestQueue.push({ url, config, retryTimes, retryDelay, resolve, reject }); // 执行 processQueue 方法处理请求队列中的每个请求 processQueue(); }); } module.exports = { request };

总结

并发控制 + 请求重试整体思路还是比较简单的, Promise + 队列是关键。大家可以基于文中的代码扩充自己的业务逻辑。

这套思路可使用的场景有很多, 比如 通过 refreshToken 刷新 token、URL 携带 ticket 实现免登录 等。

通过使用并发限制和重试机制, 我们可以更好地控制前端请求的发送和处理。在实际开发中, 我们需要根据具体的业务场景来选择合适的并发限制和重试机制, 以确保请求的成功率和性能。

Last updated on