Node.js
前言:今天学习的时候看到回调地狱这个老生常谈的场景,以前都是一概而过,今天深入了解并且将其剖析了一下,总结出以下笔记:
node.js回调函数
node.js是单线程 的,但通过事件驱动 和回调机制实现异步操作
回调地狱
多个异步操作按顺序执行多层嵌套,导致代码的可读性和可维护性低:如
javascript
fs.readFile('file1.txt', 'utf8', (err, data1) => {
if (err) {
console.error('Error reading file1:', err);
return;
}
fs.readFile('file2.txt', 'utf8', (err, data2) => {
if (err) {
console.error('Error reading file2:', err);
return;
}
fs.readFile('file3.txt', 'utf8', (err, data3) => {
if (err) {
console.error('Error reading file3:', err);
return;
}
console.log('Data from all files:', data1, data2, data3);
});
});
});
解决办法:三种
promises
允许使用链式调用.then()的方式处理异步操作,避免回调地狱
javascript
const fs = require('fs').promises;
fs.readFile('file1.txt', 'utf8')
.then(data1 => {
console.log('Data from file1:', data1);
return fs.readFile('file2.txt', 'utf8');
})
.then(data2 => {
console.log('Data from file2:', data2);
return fs.readFile('file3.txt', 'utf8');
})
.then(data3 => {
console.log('Data from file3:', data3);
})
.catch(err => {
console.error('Error reading files:', err);
});
使用async/await
async/await是ES2017引入的语法糖,可以更方便地处理异步操作,避免回调地狱
javascript
const fs = require('fs').promises;
async function readFiles() {
try {
const data1 = await fs.readFile('file1.txt', 'utf8');
const data2 = await fs.readFile('file2.txt', 'utf8');
const data3 = await fs.readFile('file3.txt', 'utf8');
console.log('Data from all files:', data1, data2, data3);
} catch (err) {
console.error('Error reading files:', err);
}
}
readFiles();
事件驱动编程
首先需要明白:什么是事件驱动编程呢?
概念:事件驱动编程中,程序会等待事件的出现,一旦事件发生,就会执行预先定义好的处理函数(也称为事件监听器)。在 Node.js 里,很多对象都可以触发事件,像文件读写流、网络请求等。
事件驱动编程的工作机制
(1) 事件发射器 :Node.js 的events
模块提供了EventEmitter
类,这是事件驱动编程的基础。可以通过创建EventEmitter
的实例来创建、触发和监听事件。
(2) 事件触发: 某个特定的操作完成或者某个条件满足时,就会触发相应的事件
(3) 事件监听 :程序可以通过on
或者addListener
方法来监听特定的事件,当事件触发时,与之关联的回调函数就会被执行。
以下是一个简单的实例,实现了如何进行事件驱动编程:
javascript
const EventEmitter = require('events')
//创建一个实例对象
const myEmitter = new EventEmitter()
//定义事件监听器
const eventListener = (message) => {
console.log(`接收到:${message}`)
}
//监听自定义事件 testEvent
myEmitter.on('testEvent',eventListener)
//触发自定义事件
myEmitter.emit('testEvent','成功发送message触发自定义事件')
//移除事件监听器
myEmitter.removeListener('testEvent', eventListener);
// 再次触发事件,此时监听器已移除,不会有输出
myEmitter.emit('testEvent', '这是另一个自定义事件消息');
以上是事件驱动编程的工作机制,运行结果为:
事件驱动编程的优势
- 异步处理:事件驱动编程让 Node.js 能够高效处理异步操作,避免了阻塞线程,提升了程序的性能和响应能力。
- 高并发处理:由于可以同时监听多个事件,Node.js 能够轻松应对大量并发请求,适用于构建高性能的网络应用。
- 代码可维护性:事件驱动的设计使得代码结构更加清晰,每个事件处理函数都可以独立开发和测试,降低了代码的耦合度(模块之间的依赖程度)。
事件驱动编程解决回调地狱
模拟多个异步操作的顺序执行的解决方法:
javascript
const EventEmitter = require('events');
// 创建一个自定义事件发射器
const myEmitter = new EventEmitter();
// 模拟第一个异步操作
function asyncOperation1() {
setTimeout(() => {
console.log('异步操作 1 完成');
// 触发事件,表示操作 1 完成
myEmitter.emit('operation1Completed');
}, 1000);
}
// 模拟第二个异步操作
function asyncOperation2() {
setTimeout(() => {
console.log('异步操作 2 完成');
// 触发事件,表示操作 2 完成
myEmitter.emit('operation2Completed');
}, 1000);
}
// 模拟第三个异步操作
function asyncOperation3() {
setTimeout(() => {
console.log('异步操作 3 完成');
// 触发事件,表示操作 3 完成
myEmitter.emit('operation3Completed');
}, 1000);
}
// 监听操作 1 完成事件
myEmitter.on('operation1Completed', () => {
asyncOperation2();
});
// 监听操作 2 完成事件
myEmitter.on('operation2Completed', () => {
asyncOperation3();
});
// 启动第一个异步操作
asyncOperation1();
代码种模拟了三个定时器的异步操作,通过on 监听每个操作完成的事件,当事件触发时,执行下一个异步操作。通过调用asyncOperation1
启动整个流程,代码执行能够完美运行,解决了回调地狱问题: