为什么会出现异步(多线程能力的Node.js)
- Node.js 有一个主线程负责执行 JavaScript 代码。
- I/O 操作(文件读写、网络请求等)被【委托给系统底层】。
事件循环 (Event Loop)
-
JavaScript 代码是在一个主线程中顺序执行的,这个主线程运行着事件循环机制。
-
执行一些异步I/O操作时为了不阻塞主线程,这些任务被提交到了libuv的线程池中由后台线程执行。 当异步操作完成,它将产生一个事件或回调函数。事件循环监听这些事件或回调函数,然后将它们推入回调队列中,等待主线程空闲时处理。
看看下面这个段代码的输出结果
js
const fs = require('fs');
console.log('Start');
//宏任务
setTimeout(() => {
console.log('Timeout');
}, 0);
//异步回调
fs.readFile(__filename, () => {
console.log('Read File');
});
//宏任务
setImmediate(() => {
console.log('Immediate');
});
//微任务
process.nextTick(() => {
console.log('Next Tick');
});
console.log('End');
得到的输出结果如下:
js
Start
End
Next Tick
Timeout
Immediate
Read File
常用的异步编程机制
在 Node.js 中,异步编程有多种实现方式,包括回调函数、Promise、事件监听 和 async/await。
回调函数
回调函数在特定条件满足时(如事件触发、异步操作完成)被调用的, 即在某个操作完成后被调用的函数。它是异步编程的核心。
js
const fs = require('fs');
fs.readFile('file.txt', 'utf8',(err, data) => {
if (err) {
console.error(err);
return;
}
console.log(data);
});
优点:
- 简单易懂,容易上手。
- 可以处理简单的异步操作。
缺点:
- 回调地狱:当有多个异步操作需要依次执行时,代码会变得混乱和难以维护。
- 错误处理困难:如果一个回调函数中发生错误,很难捕获和处理这个错误。
Promise
Promise 对象表示一个异步操作的最终完成结果或失败结果, Node.js 也有基于Promise 的fs/promises模块。
以下为使用fs/promises执行读取文件的操作示例:
js
const fs = require('fs').promises;
fs.readFile('file.txt', 'utf8')
.then(data => {
console.log(data);
})
.catch(err => {
console.error(err);
});
优点:
- 可以避免多层嵌套的回调函数,使代码更加清晰和易于维。
- 可以通过链式调用
.then()和.catch()方法来处理复杂的异步流程。
缺点:
- 无法取消promise,一旦新建他就会立即执行,无法中途取消。
- 如果不设置回调函数,promise内部抛出的错误,不会反应到外部。
- 当处于pending状态时,无法得知目前的状态是刚刚开始还是即将完成。
async/await
async/await基于Promise的语法糖。
async关键字用于定义一个异步函数,这类函数始终会返回一个 Promise 对象。 而await关键字则只能在async函数内部使用,它的作用是等待一个 Promise 对象的状态变为 fulfilled 或 rejected,并暂停async函数的执行,直到所等待的 Promise 被解决。
async/await它允许我们以同步的方式编写异步代码,同时保留异步操作的非阻塞特性。
看看示例:
js
const fs = require('fs').promises;
async function readFile() {
try {
const data = await fs.readFile('file.txt', 'utf8');
console.log(data);
} catch (err) {
console.error(err);
}
}
readFile();
优点:
- 消除回调地狱,扁平化代码结构。
- 错误处理使用try/catch 比Promise 的链式调用可读性高。
缺点:
- 只能用于异步函数中,不能用于普通函数中。
- 不能处理所有类型的异步操作,例如定时器和事件。
- 可能会导致性能问题,因为它会创建额外的 Promise 对象和执行上下文。
发布订阅模式
- 多线程机制(Web Worker/worker_threads )使用的postMeassage/onmessage。
- 使用Node.js内置的events模块,如下HTTP请求
server on('request')事件监听。
js
const EventEmitter = require("events");
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter();
myEmitter.on("event", () => {
console.log("an event occurred!");
});
myEmitter.emit("event");
const http = require("http");
const server = http.createServer((req, res) => {
res.end("hello!!! this is YK!!!");
});
server.on("request", (req, res) => {
console.log(req.url);
});
server.listen(3000);