MST

星途 面试题库

面试题:JavaScript 深入探讨 Promise.all 与 Promise.race 的内部机制及性能优化

详细阐述 Promise.all 和 Promise.race 的内部实现机制,包括如何处理多个 Promise 的状态变化以及它们在事件循环中的执行逻辑。在处理大量 Promise 时,如何优化 Promise.all 和 Promise.race 的性能,避免出现内存泄漏或性能瓶颈,请结合代码示例说明。
48.7万 热度难度
编程语言JavaScript

知识考点

AI 面试

面试题答案

一键面试

Promise.all 内部实现机制

  1. 处理多个 Promise 状态变化
    • Promise.all 接受一个 Promise 数组作为参数(也可以是包含 then 方法的类似数组的对象)。
    • 它会创建一个新的 Promise。这个新 Promise 的状态取决于传入的所有 Promise 的状态。
    • 当所有传入的 Promise 都变为 resolved 时,新 Promise 才会 resolved,并且其 resolved 的值是一个数组,该数组按顺序包含了所有传入 Promise resolved 的值。
    • 如果其中任何一个 Promise 变为 rejected,新 Promise 就会立即 rejected,其 rejected 的原因就是第一个变为 rejected 的 Promise 的原因。
  2. 事件循环中的执行逻辑
    • Promise.all 是异步操作,不会阻塞事件循环。当调用 Promise.all 时,它会立即返回一个处于 pending 状态的新 Promise。
    • 然后,它会依次执行传入的每个 Promise。这些 Promise 的执行是异步的,会被放入微任务队列。
    • 当所有 Promise 都完成(resolvedrejected)后,根据上述规则,Promise.all 返回的新 Promise 会相应地变为 resolvedrejected,并将其结果放入微任务队列,等待当前宏任务执行完毕后,在微任务阶段执行。

Promise.race 内部实现机制

  1. 处理多个 Promise 状态变化
    • Promise.race 同样接受一个 Promise 数组(或类似数组对象)作为参数。
    • 它也返回一个新的 Promise。这个新 Promise 的状态会随着传入的 Promise 数组中第一个完成(resolvedrejected)的 Promise 而改变。
    • 也就是说,只要有一个 Promise 完成,Promise.race 返回的新 Promise 就会以相同的状态和值(或原因)完成。
  2. 事件循环中的执行逻辑
    • Promise.race 也是异步操作,立即返回一个 pending 状态的新 Promise。
    • 然后,它会并行执行传入的所有 Promise,这些 Promise 的执行被放入微任务队列。
    • 当第一个 Promise 完成时,Promise.race 返回的新 Promise 会以相同状态和值(或原因)完成,并将其结果放入微任务队列,等待当前宏任务执行完毕后,在微任务阶段执行。

性能优化及避免内存泄漏和性能瓶颈

  1. 限制并发数量
    • 在处理大量 Promise 时,同时执行过多的 Promise 可能会导致内存和性能问题。可以通过限制并发数量来优化。
    • 以下是使用 async/await 和队列来实现限制并发的示例:
function asyncTask() {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve();
        }, 1000);
    });
}

async function runTasks(tasks, concurrency) {
    const results = [];
    const taskQueue = [...tasks];
    const runningTasks = [];
    while (taskQueue.length > 0 || runningTasks.length > 0) {
        while (runningTasks.length < concurrency && taskQueue.length > 0) {
            const task = taskQueue.shift();
            const result = task();
            results.push(result);
            runningTasks.push(result);
        }
        await Promise.race(runningTasks);
        runningTasks.splice(runningTasks.indexOf(await Promise.race(runningTasks)), 1);
    }
    return Promise.all(results);
}

// 示例使用
const tasks = Array.from({ length: 10 }, () => asyncTask);
runTasks(tasks, 3).then(() => {
    console.log('All tasks completed');
});
  • 在上述代码中,runTasks 函数接受任务数组和并发数量作为参数。它使用一个队列来管理任务,每次只允许 concurrency 数量的任务同时执行。当有任务完成时,从队列中取出新任务继续执行,直到所有任务完成。这样可以有效避免同时执行过多任务导致的性能问题。
  1. 及时释放资源
    • 在 Promise 处理过程中,确保及时释放不再使用的资源。例如,如果 Promise 中创建了定时器、网络连接等资源,在 Promise 完成后要及时清理这些资源。
    • 以下是一个简单的示例,展示如何在 Promise 完成后清除定时器:
function timerPromise() {
    return new Promise((resolve) => {
        const timer = setTimeout(() => {
            clearTimeout(timer);
            resolve();
        }, 1000);
    });
}
  • 在这个示例中,当定时器触发时,先清除定时器,然后再 resolve Promise,避免了定时器持续占用资源导致的潜在内存泄漏。