nodeJs笔记(六)
异步编程和回调
nodejs特性之一就是异步编程 它解决了JavaScript因为单线程原因多个任务执行效率低下的问题
Node.js 采用单线程事件循环架构,这意味着:
所有用户代码在主线程运行
I/O 操作(文件、网络、数据库等)由底层线程池处理
异步模型避免阻塞主线程,实现高并发
回调函数:异步编程的基础
回调函数是 Node.js 中处理异步操作的最基本方式,它是一个在异步操作完成后被调用的函数。
回调函数的特点:
错误优先约定:回调的第一个参数通常是错误对象
异步执行:在操作完成后调用
非阻塞:不会阻塞主线程执行
// 回调函数的基本模式
function asyncOperation(param, callback) {// 模拟异步操作setTimeout(() => {const error = Math.random() > 0.8 ? new Error('操作失败') : null;const result = `处理结果: ${param}`;callback(error, result);}, 100);
}// 使用回调
asyncOperation('输入数据', (err, result) => {if (err) {console.error('发生错误:', err.message);return;}console.log('操作结果:', result);
});
Node.js 事件循环机制
┌───────────────────────────┐
┌─>│ timers │ (setTimeout, setInterval)
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ pending callbacks │ (I/O 回调)
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ idle, prepare │ (内部使用)
│ └─────────────┬─────────────┘ ┌───────────────┐
│ ┌─────────────┴─────────────┐ │ incoming: │
│ │ poll │<─────┤ connections, │
│ └─────────────┬─────────────┘ │ data, etc. │
│ ┌─────────────┴─────────────┐ └───────────────┘
│ │ check │ (setImmediate)
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
└──┤ close callbacks │ (关闭事件回调)└───────────────────────────┘
回调地狱问题与解决方案
示例:
fs.readFile('file1.txt', 'utf8', (err, data1) => {if (err) return console.error(err);fs.readFile('file2.txt', 'utf8', (err, data2) => {if (err) return console.error(err);fs.writeFile('combined.txt', data1 + data2, (err) => {if (err) return console.error(err);console.log('文件合并成功');fs.readFile('combined.txt', 'utf8', (err, combined) => {if (err) return console.error(err);console.log('合并内容:', combined);});});});
});
解决方案:
- 命名函数:避免嵌套
function handleFile1(err, data1) {if (err) return console.error(err);fs.readFile('file2.txt', 'utf8', handleFile2.bind(null, data1));
}function handleFile2(data1, err, data2) {if (err) return console.error(err);fs.writeFile('combined.txt', data1 + data2, handleWrite);
}// ...继续分解
- 使用 Promise
const fs = require('fs').promises;fs.readFile('file1.txt', 'utf8').then(data1 => fs.readFile('file2.txt', 'utf8').then(data2 => [data1, data2])).then(([data1, data2]) => fs.writeFile('combined.txt', data1 + data2)).then(() => fs.readFile('combined.txt', 'utf8')).then(combined => console.log('合并内容:', combined)).catch(err => console.error(err));
- 使用 async/await
async function combineFiles() {try {const data1 = await fs.promises.readFile('file1.txt', 'utf8');const data2 = await fs.promises.readFile('file2.txt', 'utf8');await fs.promises.writeFile('combined.txt', data1 + data2);const combined = await fs.promises.readFile('combined.txt', 'utf8');console.log('合并内容:', combined);} catch (err) {console.error(err);}
}
常见异步模式
4. 并行执行
const fs = require('fs');let completed = 0;
const results = [];function checkCompletion() {if (++completed === 3) {console.log('所有操作完成:', results);}
}fs.readFile('file1.txt', 'utf8', (err, data) => {if (!err) results[0] = data;checkCompletion();
});fs.readFile('file2.txt', 'utf8', (err, data) => {if (!err) results[1] = data;checkCompletion();
});fs.readFile('file3.txt', 'utf8', (err, data) => {if (!err) results[2] = data;checkCompletion();
});
使用 Promise.all 改进
const fs = require('fs').promises;Promise.all([fs.readFile('file1.txt', 'utf8'),fs.readFile('file2.txt', 'utf8'),fs.readFile('file3.txt', 'utf8')
]).then(results => {console.log('所有操作完成:', results);}).catch(err => {console.error('发生错误:', err);});
注意:
始终处理错误:避免忽略回调中的错误
避免深度嵌套:使用命名函数或 Promise 解耦
使用 async/await:对于现代 Node.js 应用
遵循错误优先模式:保持代码一致性
使用工具库:如 async 模块处理复杂流程
合理使用事件发射器:对于重复性事件
const { EventEmitter } = require('events');class FileProcessor extends EventEmitter {process(file) {fs.readFile(file, 'utf8', (err, data) => {if (err) {this.emit('error', err);return;}try {const result = this.transform(data);this.emit('processed', file, result);} catch (transformErr) {this.emit('error', transformErr);}});}transform(data) {// 数据处理逻辑return data.toUpperCase();}
}// 使用
const processor = new FileProcessor();
processor.on('processed', (file, result) => {console.log(`文件 ${file} 处理完成: ${result.slice(0, 20)}...`);
});
processor.on('error', err => console.error('处理错误:', err));processor.process('example.txt');
最佳实践
1. 始终处理错误
async function main() {try {// 业务逻辑} catch (error) {console.error('Unhandled error:', error);process.exit(1);}
}
2. 合理使用 Promise 方法
// 使用Promise.allSettled获取所有结果
async function batchProcess(items) {const results = await Promise.allSettled(items.map(processItem));const successes = results.filter(r => r.status === 'fulfilled').map(r => r.value);const errors = results.filter(r => r.status === 'rejected').map(r => r.reason);return { successes, errors };
}
3. 避免阻塞事件循环
// 将CPU密集型任务移出主线程
async function heavyComputation(data) {// 使用worker_threads或child_processreturn new Promise((resolve, reject) => {const worker = new Worker('./compute.js');worker.postMessage(data);worker.on('message', resolve);worker.on('error', reject);});
}
常见陷阱与解决方案
- 忘记 await
async function saveUser(user) {// 错误:忘记await,返回Promise而不是结果return db.save(user);
}// 正确
async function saveUser(user) {await db.save(user);return user;
}
- 循环中的 await
// 错误:顺序执行导致性能问题
async function processArray(array) {for (const item of array) {await process(item); // 逐个等待}
}// 正确:并行处理
async function processArray(array) {await Promise.all(array.map(process));
}
- 错误传播中断
async function parent() {try {await child1(); // 如果出错,child2不会执行await child2();} catch (e) {// 处理错误}
}// 解决方案:独立处理
async function parent() {try {await Promise.all([child1().catch(handleError),child2().catch(handleError)]);} catch (e) {// 处理未捕获错误}
}
Node.js 特定应用
- 与 Streams 结合
const { pipeline } = require('stream/promises');async function processFile(input, output) {await pipeline(fs.createReadStream(input),zlib.createGzip(),fs.createWriteStream(output));console.log('Pipeline succeeded');
}
- 与 EventEmitter 集成
const { once } = require('events');async function waitForDatabase() {const db = new Database();try {await once(db, 'connect');console.log('Database connected');// 等待多个事件const [result] = await Promise.race([once(db, 'queryComplete'),once(db, 'error')]);return result;} catch (err) {console.error('Database error:', err);throw err;}
}
总结
Node.js 的异步编程是其高性能的核心:
回调是基础但易导致"回调地狱"
Promise 和 async/await 提供了更优雅的解决方案
理解事件循环机制对编写高效代码至关重要
结合事件发射器可构建更复杂的异步系统
现代应用应优先使用 Promise 和 async/await,同时理解回调机制对于维护遗留代码和深入理解 Node.js 运行原理仍然非常重要。