Skip to content
JS 并发请求含失败重试并按顺序拿数据

下面的demo模拟了js并发20个请求,,如果失败了则失败的这一条会重试请求,在此过程中,只要有一个接口重试超过五次就提示获取数据失败并停止后续并发请求,如果所有接口都成功了则提示所有数据获取成功。

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <title>批量并发请求 Demo</title>
  <style>
    body { font-family: Consolas, Monaco, monospace; margin: 20px; }
    #log { height: 80vh; overflow-y: auto; border: 1px solid #ccc; padding: 8px; background: #fafafa; }
    button { padding: 8px 16px; font-size: 16px; }
    .suc { color: green; }
    .fail { color: red; }
  </style>
</head>
<body>
  <h2>批量并发请求测试</h2>
  <button id="startBtn">开始并发(1-1001)</button>
  <pre id="log"></pre>

  <script>
    const logEl = document.getElementById('log');
    function log(msg, cls = '') {
      const div = document.createElement('div');
      div.textContent = `[${new Date().toLocaleTimeString()}] ${msg}`;
      if (cls) div.className = cls;
      logEl.appendChild(div);
      console.log(msg);
    }

    /* ====== 模拟请求:带随机失败概率 ====== */
    function mockRequest(id) {
      return new Promise((resolve, reject) => {
        // const delay = Math.random() * 300 + 100; // 100-400ms
        const delay = 2000; // 100-400ms
        setTimeout(() => {
          if (Math.random() < 0.15) {            // 15% 概率失败
            reject(new Error(`请求 ${id} 失败`));
          } else {
            resolve(`内容-${id}`);
          }
        }, delay);
      });
    }

    /* ====== 单个元素重试封装 ====== */
    async function fetchWithRetry(id, retry = 5) {
      for (let i = 1; i <= retry; i++) {
        try {
          const data = await mockRequest(id);
          log(`✅ 请求 ${id} 成功(第${i}次)`, 'suc');
          return { id, data };
        } catch (e) {
          log(`❌ 请求 ${id} 失败(第${i}次)`, 'fail');
          if (i === retry) throw e; // 达到最大重试
        }
      }
    }

    /* ====== 并发控制主函数 ====== */
    async function concurrentFetch(list, concurrency = 20) {
      const result = new Array(list.length); // 保证顺序
      let index = 0;
      let done = 0;
      let aborted = false;

      async function worker() {
        while (index < list.length && !aborted) {
          const curIdx = index++; // 当前任务下标
          const id = list[curIdx];
          try {
            const { data } = await fetchWithRetry(id);
            result[curIdx] = data;
            done++;
          } catch (e) {
            log(`🚫 元素 ${id} 重试5次均失败,立即中断!`, 'fail');
            aborted = true; // 中断后续
            return;
          }
        }
      }

      /* 启动 concurrency 个 worker */
      const workers = Array.from({ length: concurrency }, () => worker());
      await Promise.all(workers);

      if (aborted) {
        log('🔴 批量请求失败(存在彻底失败元素)');
        throw new Error('aborted');
      }
      log('🎉 全部请求完成(含重试成功)');
      return result;
    }

    /* ====== 按钮触发 ====== */
    document.getElementById('startBtn').addEventListener('click', async () => {
      logEl.innerHTML = '';
      const data = Array.from({ length: 1001 }, (_, i) => i + 1); // [1..1001]
      try {
        const res = await concurrentFetch(data, 20);
        log(`✅ 最终合并结果长度:${res.length}`);
        console.log('合并数组:', res);
      } catch (e) {
        log('❌ 批量中断,合并失败');
      }
    });
  </script>
</body>
</html>