MST

星途 面试题库

面试题:JavaScript异步操作中的并发与竞争

假设有多个异步任务,例如使用Promise发起多个网络请求。请阐述如何实现并发控制,即同时执行一定数量的任务,并且在所有任务完成后进行统一处理?另外,如何处理任务之间的竞争关系,比如只获取最先完成的任务结果?
47.6万 热度难度
编程语言JavaScript

知识考点

AI 面试

面试题答案

一键面试

并发控制

  1. 使用 Promise.allSettled 和队列
    • 创建一个任务队列,将所有异步任务放入队列中。
    • 维护一个正在执行的任务数组,同时执行的任务数量不超过设定的并发数。
    • 当有任务完成时,从正在执行的任务数组中移除,并从任务队列中取出新的任务执行,直到任务队列耗尽。
    • 最后使用 Promise.allSettled 等待所有任务(包括已经完成和正在执行的)都结束,统一处理结果。 示例代码如下:
function limitConcurrentTasks(tasks, limit) {
    return new Promise((resolve, reject) => {
        const results = [];
        let completedCount = 0;
        const executingTasks = [];
        const executeNext = () => {
            while (executingTasks.length < limit && tasks.length > 0) {
                const task = tasks.shift();
                const promise = task();
                executingTasks.push(promise);
                promise.then((res) => {
                    results.push({ status: 'fulfilled', value: res });
                }).catch((err) => {
                    results.push({ status:'rejected', reason: err });
                }).finally(() => {
                    completedCount++;
                    const index = executingTasks.indexOf(promise);
                    if (index!== -1) {
                        executingTasks.splice(index, 1);
                    }
                    if (completedCount === tasks.length + executingTasks.length) {
                        resolve(results);
                    } else {
                        executeNext();
                    }
                });
            }
        };
        executeNext();
    });
}
// 使用示例
const tasks = [
    () => new Promise((resolve) => setTimeout(() => resolve(1), 1000)),
    () => new Promise((resolve) => setTimeout(() => resolve(2), 1500)),
    () => new Promise((resolve) => setTimeout(() => resolve(3), 500))
];
limitConcurrentTasks(tasks, 2).then((results) => {
    console.log(results);
});
  1. 使用 async/await 和循环
    • 同样先将所有任务放入数组,通过循环和 await 来控制并发数量。
    • 每次循环中创建一定数量的任务,使用 Promise.all 等待这些任务完成,然后再进行下一轮循环,直到所有任务处理完毕。
    • 示例代码:
async function limitConcurrentTasks2(tasks, limit) {
    const results = [];
    for (let i = 0; i < tasks.length; i += limit) {
        const currentTasks = tasks.slice(i, i + limit);
        const currentResults = await Promise.all(currentTasks.map(task => task()));
        results.push(...currentResults);
    }
    return results;
}
// 使用示例
const tasks2 = [
    () => new Promise((resolve) => setTimeout(() => resolve(1), 1000)),
    () => new Promise((resolve) => setTimeout(() => resolve(2), 1500)),
    () => new Promise((resolve) => setTimeout(() => resolve(3), 500))
];
limitConcurrentTasks2(tasks2, 2).then((results) => {
    console.log(results);
});

处理任务竞争关系(获取最先完成的任务结果)

  1. 使用 Promise.race
    • Promise.race 方法接收一个 Promise 数组,返回一个新的 Promise。
    • 这个新的 Promise 会在数组中的任何一个 Promise 被解决(resolved)或被拒绝(rejected)时,立刻以相同的状态被解决或被拒绝。
    • 示例代码:
const task1 = new Promise((resolve) => setTimeout(() => resolve('Task 1'), 1500));
const task2 = new Promise((resolve) => setTimeout(() => resolve('Task 2'), 1000));
const task3 = new Promise((resolve) => setTimeout(() => resolve('Task 3'), 2000));
Promise.race([task1, task2, task3]).then((result) => {
    console.log(result); // 输出 'Task 2',因为它最先完成
});
  1. 手动实现竞争逻辑
    • 可以通过给每个任务添加一个定时器来模拟竞争,先完成的任务会触发对应的回调。
    • 示例代码:
function raceTasks(tasks) {
    return new Promise((resolve, reject) => {
        tasks.forEach(task => {
            task().then(resolve).catch(reject);
        });
    });
}
// 使用示例
const taskA = () => new Promise((resolve) => setTimeout(() => resolve('Task A'), 1500));
const taskB = () => new Promise((resolve) => setTimeout(() => resolve('Task B'), 1000));
const taskC = () => new Promise((resolve) => setTimeout(() => resolve('Task C'), 2000));
raceTasks([taskA, taskB, taskC]).then((result) => {
    console.log(result); // 输出 'Task B',因为它最先完成
});