Appearance
事件循环
一般的服务器会为每个用户请求单独生成线程来服务,但线程的管理是比较耗时的。Node.js本身是以单线程的模式运行的,即所有用户请求使用一个主线程完成,但这种方式如果多个用户请求时会发生阻塞,请求会排队执行。
为了解决这个问题,Node 内部使用 Libuv 实现异步非阻塞的任务处理。
- libuv是使用C语言编写的跨平台开源库
- libuv是处理Node.js中异步非阻塞操作的
- libuv使用线程池和事件循环处理异步操作

Node 主线程维护事件队列,接收到请求后放入事件队列中,然后继承接收其他请求。
主线程空闲时,循环事件队列,检查队列中是否有任务,这时根据不同的任务使用不同的处理方式
- 如果是非I/O 操作,就通过主线程执行,然后通过回调函数返回到上层调用
- 如果是 I/O 操作就交给 Libuv 使用多线程处理,处理后再以回调函数形式返回到事件队列中(执行上步操作)
简单总结:
非 IO 操作:接收请求-> 放入任务队列->事件循环->返回上传调用
I/O 操作:接收请求-> 放入任务队列->Libuv 线程池处理->返回任务队列->事件循环->返回上传调用
线程池
node.js中以Sync为后缀的方法,一般为同步方法。同步方法会在node.js主线程中执行,会发生阻塞。
import { pbkdf2Sync } from 'crypto'
const begin = Date.now()
const key1 = pbkdf2Sync('banmashou.com', 'salt', 100000, 64, 'sha512')
const key2 = pbkdf2Sync('banmashou.com', 'salt', 100000, 64, 'sha512')
const key3 = pbkdf2Sync('banmashou.com', 'salt', 100000, 64, 'sha512')
console.log(Date.now() - begin)
下面将crypto加密操作使用异步执行,这时node.js 主线程会将任务交给libuv线程池执行,不阻塞主线程。因为libuv是多线程,这时执行时间就少很多。当libuv执行完后调用node.js主线程中的回调函数。
import { pbkdf2, pbkdf2Sync } from 'crypto'
const begin = Date.now()
for (let i = 0; i < 3; i++) {
pbkdf2('banmashou.com', 'salt', 100000, 64, 'sha512', () => {
console.log(`${i} - `, Date.now() - begin)
})
}
回调函数
下面通过文件写入的例子,说明回调函数的工作机制,并不会真正写入文件。主要是帮助大家对上面的事件循环加深理解。
function writeFile(file: string, content: string, callback: (error: any) => void) {
//执行到这里,放入异步队列
setTimeout(() => {
try {
console.log('%s 文件写入成功,内容是 %s', file, content)
//任务执行后,调用回调函数
callback(null)
} catch (error: any) {
callback(error)
}
}, 2000)
}
writeFile('bm.txt', 'banmashou.com', (error) => {
error ? console.log(error) : console.log('文件保存成功')
})
Promise
使用 Promise 与 Async 与 Await 可以使用异步的编写更清晰,并避免回调函数带来的嵌套问题。