普通视图

发现新文章,点击刷新页面。
昨天 — 2025年8月18日首页

异步任务并发控制

2025年8月18日 17:52

JavaScript 异步任务并发控制

🤔 问题背景

常见面试题 批量并发任务

📋 需求分析

一个完整的并发控制方案需要满足以下要求:

  • 并发限制:同时执行的任务数量不能超过指定上限
  • 任务队列:待执行的任务需要有序排队
  • 结果保序:无论任务何时完成,最终结果要按原始顺序返回
  • 错误处理:任何一个任务失败时,能够优雅地处理错误
  • 动态调度:任务完成后自动开始下一个待执行任务

💡 核心实现

让我们来看一个优雅的实现方案:

const runTask = async (tasks, maxTaskNum) => {
  // 参数校验和初始化
  const total = Array.isArray(tasks) ? tasks.length : 0;
  if (total === 0) return [];

  const limit = Math.max(1, Math.min(maxTaskNum, total));
  const result = new Array(total);

  // 使用Promise.withResolvers()创建可控制的Promise
  const { promise, resolve, reject } = Promise.withResolvers();
  let nextIndex = 0; // 下一个要执行的任务索引
  let finished = 0; // 已完成的任务计数

  const runNext = () => {
    const i = nextIndex++;
    if (i >= total) return; // 没有更多任务

    Promise.resolve()
      .then(() => tasks[i]())
      .then((res) => {
        result[i] = res; // 按索引存储,保证顺序
      })
      .catch((err) => {
        reject(err); // 任何一个任务失败,整体失败
      })
      .finally(() => {
        finished++;
        if (finished === total) {
          resolve(result); // 所有任务完成
        } else {
          runNext(); // 继续执行下一个任务
        }
      });
  };

  // 启动初始的并发任务
  for (let i = 0; i < limit; i++) {
    runNext();
  }

  await promise;
  return result;
};

🔍 代码详解

1. 参数处理与初始化

const total = Array.isArray(tasks) ? tasks.length : 0;
if (total === 0) return [];

const limit = Math.max(1, Math.min(maxTaskNum, total));
const result = new Array(total);

这部分代码确保了参数的合法性:

  • 验证任务数组的有效性
  • 计算实际并发数(不能超过总任务数,至少为 1)
  • 预先创建结果数组,确保索引对应关系

2. Promise 控制器

const { promise, resolve, reject } = Promise.withResolvers();

Promise.withResolvers()是 ES2024 的新特性,它返回一个包含 promise 及其控制函数的对象,让我们可以在外部控制 Promise 的状态。

3. 任务调度核心

const runNext = () => {
  const i = nextIndex++;
  if (i >= total) return;

  Promise.resolve()
    .then(() => tasks[i]())
    .then((res) => (result[i] = res))
    .catch((err) => reject(err))
    .finally(() => {
      finished++;
      if (finished === total) {
        resolve(result);
      } else {
        runNext();
      }
    });
};

这是整个调度器的核心逻辑:

  • nextIndex++:原子性地获取下一个任务索引
  • Promise.resolve().then():确保任务异步执行
  • result[i] = res:按原始索引存储结果
  • finally块:无论成功失败都要更新计数和调度

🎯 执行流程演示

让我们通过一个具体例子来理解执行流程:

runTask(
  [
    () => new Promise((resolve) => setTimeout(() => resolve(1), 6000)), // 6秒
    () => new Promise((resolve) => setTimeout(() => resolve(2), 1000)), // 1秒
    () => new Promise((resolve) => setTimeout(() => resolve(3), 100)), // 0.1秒
    () => new Promise((resolve) => setTimeout(() => resolve(4), 2000)), // 2秒
    () => new Promise((resolve) => setTimeout(() => resolve(5), 100)), // 0.1秒
  ],
  2
).then((res) => {
  console.log(res); // [1, 2, 3, 4, 5]
});

执行时间线(并发数=2):

0ms:     启动任务0(6s) 和 任务1(1s)      [执行中: 0,1]
1000ms:  任务1完成,启动任务2(0.1s)      [执行中: 0,2]
1100ms:  任务2完成,启动任务3(2s)        [执行中: 0,3]
3100ms:  任务3完成,启动任务4(0.1s)      [执行中: 0,4]
3200ms:  任务4完成,等待任务0            [执行中: 0]
6000ms:  任务0完成,所有任务结束         [完成]

总耗时: 6秒(相比串行执行的9.3秒,节省了3.3秒)

🚀 优化和扩展

1. 添加进度回调

const runTaskWithProgress = async (tasks, maxTaskNum, onProgress) => {
    // ... 原有代码

    .finally(() => {
        finished++;
        onProgress && onProgress({
            finished,
            total,
            percent: (finished / total * 100).toFixed(2)
        });
        // ... 后续逻辑
    });
};

2. 支持任务优先级

const runTaskWithPriority = async (tasks, maxTaskNum) => {
  // 按优先级排序任务
  const sortedTasks = tasks
    .map((task, index) => ({ task, index, priority: task.priority || 0 }))
    .sort((a, b) => b.priority - a.priority);

  // ... 使用排序后的任务执行
};

3. 失败重试机制

const executeWithRetry = async (task, retries = 3) => {
  for (let i = 0; i < retries; i++) {
    try {
      return await task();
    } catch (error) {
      if (i === retries - 1) throw error;
      await new Promise((resolve) => setTimeout(resolve, 1000 * i));
    }
  }
};

⚡ 性能考量

  1. 内存使用:预先创建结果数组会占用内存,对于大量任务需要考虑分批处理
  2. 错误处理:当前实现遇到错误会立即终止,可以考虑支持部分失败
  3. 取消机制:长时间运行的任务可能需要支持取消操作

🎉 总结

异步任务并发控制是前端开发中的重要技能,它能够:

  • 提升性能:合理利用并发,减少总执行时间
  • 保护资源:避免过度并发造成的资源浪费
  • 增强体验:提供可控的执行进度和错误处理

通过理解其核心原理和实现细节,我们可以根据具体场景进行定制和优化,构建出更加 robust 和高效的异步任务处理方案。

这种模式在现代前端框架中也有广泛应用,比如 Vue 的异步组件加载、React 的 Suspense 机制等,都体现了类似的并发控制思想。掌握这种技术,将让你在处理复杂异步场景时更加得心应手。


如果这篇文章对你有帮助,欢迎点赞 👍、收藏 ⭐ 和分享 📤!

关注我,获取更多前端技术干货和面试题解析 🚀

❌
❌