面试题答案
一键面试JavaScript Promise 工作原理
- 状态管理:Promise 有三种状态:
pending
(进行中)、fulfilled
(已成功)和rejected
(已失败)。初始状态为pending
。一旦从pending
状态转换到fulfilled
或rejected
状态,就不会再改变。 - 构造函数与执行:通过
new Promise((resolve, reject) => { /* 执行器函数 */ })
创建 Promise。执行器函数会立即同步执行,其中resolve
用于将 Promise 状态变为fulfilled
,reject
用于将状态变为rejected
。 - 链式调用:Promise 的
then
方法返回一个新的 Promise,这使得可以进行链式调用。then
方法接受两个回调函数,第一个处理fulfilled
状态,第二个处理rejected
状态。如果前一个 Promise 成功,会调用下一个then
的第一个回调;如果失败,会调用第二个回调。
微任务队列在 Promise 执行过程中的作用
- 微任务概念:微任务是一种在当前任务执行结束后,下一个宏任务执行之前执行的任务。
- Promise 与微任务:当 Promise 的状态发生改变(从
pending
变为fulfilled
或rejected
)时,then
方法中的回调函数会被放入微任务队列。这意味着即使当前的同步代码还未执行完毕,只要当前调用栈清空,微任务队列中的任务就会被执行。例如:
console.log('start');
new Promise((resolve) => {
console.log('promise executor');
resolve();
}).then(() => {
console.log('promise then');
});
console.log('end');
// 输出:start -> promise executor -> end -> promise then
在这个例子中,promise then
会在当前同步代码(console.log('end')
)执行完毕后,下一个宏任务开始前执行,因为 then
回调被放入了微任务队列。
高并发场景下 Promise 使用的性能优化及与事件循环机制结合
- 事件循环机制:JavaScript 是单线程的,通过事件循环机制来处理异步任务。事件循环会不断检查调用栈是否为空,当调用栈为空时,会从宏任务队列中取出一个任务放入调用栈执行,执行完后再处理微任务队列中的所有任务,如此循环。宏任务包括
setTimeout
、setInterval
、I/O
操作等,微任务包括Promise.then
、MutationObserver
等。 - 优化策略:
- 限制并发数量:使用
Promise.allSettled
结合async/await
来控制并发数量。例如,可以创建一个函数来处理一组任务,每次只执行固定数量的任务,当有任务完成时,再启动新的任务。
- 限制并发数量:使用
async function runTasks(tasks, concurrency) {
const results = [];
const running = [];
for (let i = 0; i < tasks.length; i++) {
const task = tasks[i];
const promise = Promise.resolve(task());
results.push(promise);
running.push(promise);
if (running.length >= concurrency) {
await Promise.race(running);
running.splice(running.indexOf(await Promise.race(running)), 1);
}
}
return Promise.allSettled(results);
}
- **避免不必要的 Promise 创建**:如果某些操作可以同步完成,就不要用 Promise 包装。
- **合理利用微任务队列**:因为微任务会在宏任务之前执行,所以在高并发场景下,如果微任务队列中的任务过多,可能会导致宏任务长时间得不到执行,造成页面卡顿等问题。要尽量控制微任务的数量和执行时间。可以将一些非紧急的任务放入宏任务队列(如使用 `setTimeout` 延迟执行)。