面试题答案
一键面试事件循环机制
- 概述:Node.js 是单线程的,事件循环是其实现异步操作的核心机制。它允许 Node.js 在非阻塞的方式下处理 I/O 操作,通过循环不断检查事件队列,处理其中的任务。
- 事件循环阶段:
- timers:这个阶段执行
setTimeout
和setInterval
设定的回调函数。 - I/O callbacks:处理一些系统级别的回调,比如 TCP 连接错误等。
- idle, prepare:仅内部使用。
- poll:这是事件循环中最重要的阶段,在这个阶段会执行以下操作:
- 如果有定时器到期,会执行定时器回调,进入
timers
阶段。 - 如果有新的 I/O 事件,会执行对应的 I/O 回调。
- 如果事件队列中没有待处理事件,且没有定时器设定,Node.js 会在此阶段阻塞等待新事件。
- 如果有定时器到期,会执行定时器回调,进入
- check:执行
setImmediate
设定的回调函数。 - close callbacks:执行一些关闭的回调,比如
socket.on('close', ...)
。
- timers:这个阶段执行
处理异步 I/O 操作和定时器
- 异步 I/O 操作:当 Node.js 执行到异步 I/O 操作(如文件读取、网络请求)时,它不会阻塞线程等待操作完成。而是将该操作交给底层的 I/O 线程池(在 Node.js 中,不同的 I/O 操作可能使用不同的线程池)去处理,然后继续执行事件循环中的其他任务。当 I/O 操作完成后,将对应的回调函数放入事件队列,等待事件循环处理。
- 定时器:
setTimeout
和setInterval
会将回调函数放入timers
队列。在事件循环的timers
阶段,会检查定时器是否到期,如果到期则将回调函数放入事件队列等待执行。不过,定时器的执行时间并不是绝对精确的,因为事件循环只有在到达timers
阶段才会检查定时器,并且如果前面的阶段执行时间过长,可能会导致定时器回调延迟执行。
使用 Promise、async/await 管理异步流程
- Promise:
- 基本概念:Promise 是一个代表异步操作最终完成(或失败)及其结果的对象。它有三种状态:
pending
(进行中)、fulfilled
(已成功)和rejected
(已失败)。一旦状态改变,就不会再变。 - 解决回调地狱:通过链式调用
.then()
方法来处理异步操作的成功和失败情况,避免了层层嵌套的回调函数。例如:
- 基本概念:Promise 是一个代表异步操作最终完成(或失败)及其结果的对象。它有三种状态:
function asyncOperation() {
return new Promise((resolve, reject) => {
// 模拟异步操作
setTimeout(() => {
resolve('Operation completed');
}, 1000);
});
}
asyncOperation()
.then(result => {
console.log(result);
return anotherAsyncOperation();
})
.then(anotherResult => {
console.log(anotherResult);
})
.catch(error => {
console.error(error);
});
- async/await:
- 基本概念:
async
函数是一种异步函数,它返回一个 Promise 对象。await
关键字只能在async
函数内部使用,它会暂停async
函数的执行,等待 Promise 被解决(resolved 或 rejected),然后恢复async
函数的执行,并返回 Promise 的解决值。 - 解决回调地狱:
async/await
使得异步代码看起来像同步代码,极大地提高了代码的可读性。例如:
- 基本概念:
async function main() {
try {
const result1 = await asyncOperation();
console.log(result1);
const result2 = await anotherAsyncOperation();
console.log(result2);
} catch (error) {
console.error(error);
}
}
main();
通过使用 Promise 和 async/await,我们可以将异步操作以一种更优雅、更易于理解和维护的方式进行管理,有效避免了回调地狱的问题。