Appearance
第6章:Node.js 异步编程
6.1 同步与异步的区别
同步编程
同步编程是指代码按照顺序执行,前一个操作完成后,才会执行下一个操作。如果前一个操作耗时较长,会阻塞后续代码的执行。
特点:
- 代码执行顺序与编写顺序一致
- 容易理解和调试
- 可能会阻塞程序执行
示例:
javascript
// 同步操作
console.log('开始');
// 模拟耗时操作
function sleep(ms) {
const start = Date.now();
while (Date.now() - start < ms) {
// 空循环,模拟耗时操作
}
}
sleep(2000); // 阻塞 2 秒
console.log('结束');输出结果:
开始
// 等待 2 秒
结束异步编程
异步编程是指代码不按照顺序执行,前一个操作开始后,不会等待其完成,而是继续执行后续代码。当异步操作完成时,会通过回调函数、Promise 或 async/await 通知主线程。
特点:
- 代码执行顺序与编写顺序不一定一致
- 不会阻塞程序执行
- 提高程序的并发性能
- 代码复杂度增加
示例:
javascript
// 异步操作
console.log('开始');
// 模拟异步操作
setTimeout(() => {
console.log('异步操作完成');
}, 2000);
console.log('结束');输出结果:
开始
结束
// 等待 2 秒
异步操作完成6.2 异步编程的三种方式
1. 回调函数
回调函数是最基础的异步编程方式,将回调函数作为参数传递给异步函数,当异步操作完成时,调用回调函数。
示例:
javascript
const fs = require('fs');
// 异步读取文件(使用回调函数)
fs.readFile('example.txt', 'utf8', (err, data) => {
if (err) {
console.error('读取文件失败:', err);
return;
}
console.log('文件内容:', data);
});
console.log('读取文件操作已开始');2. Promise
Promise 是 ES6 引入的异步编程解决方案,用于解决回调地狱问题。
基本用法:
javascript
const fs = require('fs').promises;
// 使用 Promise 读取文件
fs.readFile('example.txt', 'utf8')
.then(data => {
console.log('文件内容:', data);
})
.catch(err => {
console.error('读取文件失败:', err);
});
console.log('读取文件操作已开始');创建 Promise:
javascript
// 创建 Promise
const promise = new Promise((resolve, reject) => {
// 异步操作
setTimeout(() => {
const success = true;
if (success) {
resolve('操作成功');
} else {
reject('操作失败');
}
}, 1000);
});
// 使用 Promise
promise
.then(result => {
console.log('成功:', result);
})
.catch(error => {
console.error('失败:', error);
});3. async/await
async/await 是 ES8 引入的异步编程语法糖,基于 Promise,使异步代码看起来像同步代码。
基本用法:
javascript
const fs = require('fs').promises;
// 使用 async/await
async function readFile() {
try {
const data = await fs.readFile('example.txt', 'utf8');
console.log('文件内容:', data);
} catch (err) {
console.error('读取文件失败:', err);
}
}
readFile();
console.log('读取文件操作已开始');注意:
async函数返回一个 Promiseawait只能在async函数中使用await会等待 Promise 解决(resolve)或拒绝(reject)
6.3 回调地狱及其解决方案
回调地狱
回调地狱是指多层嵌套的回调函数,使代码难以阅读和维护。
示例:
javascript
// 回调地狱
fs.readFile('file1.txt', 'utf8', (err, data1) => {
if (err) {
console.error('读取 file1.txt 失败:', err);
return;
}
console.log('file1.txt 内容:', data1);
fs.readFile('file2.txt', 'utf8', (err, data2) => {
if (err) {
console.error('读取 file2.txt 失败:', err);
return;
}
console.log('file2.txt 内容:', data2);
fs.readFile('file3.txt', 'utf8', (err, data3) => {
if (err) {
console.error('读取 file3.txt 失败:', err);
return;
}
console.log('file3.txt 内容:', data3);
});
});
});解决方案
1. 使用 Promise 链式调用
javascript
const fs = require('fs').promises;
// 使用 Promise 链式调用
fs.readFile('file1.txt', 'utf8')
.then(data1 => {
console.log('file1.txt 内容:', data1);
return fs.readFile('file2.txt', 'utf8');
})
.then(data2 => {
console.log('file2.txt 内容:', data2);
return fs.readFile('file3.txt', 'utf8');
})
.then(data3 => {
console.log('file3.txt 内容:', data3);
})
.catch(err => {
console.error('读取文件失败:', err);
});2. 使用 async/await
javascript
const fs = require('fs').promises;
// 使用 async/await
async function readFiles() {
try {
const data1 = await fs.readFile('file1.txt', 'utf8');
console.log('file1.txt 内容:', data1);
const data2 = await fs.readFile('file2.txt', 'utf8');
console.log('file2.txt 内容:', data2);
const data3 = await fs.readFile('file3.txt', 'utf8');
console.log('file3.txt 内容:', data3);
} catch (err) {
console.error('读取文件失败:', err);
}
}
readFiles();6.4 异步实操案例
案例1:文件读取的异步实现
使用回调函数:
javascript
const fs = require('fs');
// 回调函数版本
function readFileCallback() {
console.log('开始读取文件');
fs.readFile('example.txt', 'utf8', (err, data) => {
if (err) {
console.error('读取文件失败:', err);
return;
}
console.log('文件内容:', data);
console.log('读取文件完成');
});
console.log('读取文件操作已启动');
}
readFileCallback();使用 Promise:
javascript
const fs = require('fs').promises;
// Promise 版本
function readFilePromise() {
console.log('开始读取文件');
fs.readFile('example.txt', 'utf8')
.then(data => {
console.log('文件内容:', data);
console.log('读取文件完成');
})
.catch(err => {
console.error('读取文件失败:', err);
});
console.log('读取文件操作已启动');
}
readFilePromise();使用 async/await:
javascript
const fs = require('fs').promises;
// async/await 版本
async function readFileAsyncAwait() {
console.log('开始读取文件');
try {
const data = await fs.readFile('example.txt', 'utf8');
console.log('文件内容:', data);
console.log('读取文件完成');
} catch (err) {
console.error('读取文件失败:', err);
}
console.log('读取文件操作已启动');
}
readFileAsyncAwait();案例2:接口请求的异步实现
使用 axios 发送 HTTP 请求:
bash
npm install axios使用 Promise:
javascript
const axios = require('axios');
// Promise 版本
function fetchDataPromise() {
console.log('开始请求数据');
axios.get('https://api.example.com/data')
.then(response => {
console.log('请求成功:', response.data);
})
.catch(error => {
console.error('请求失败:', error);
});
console.log('请求操作已启动');
}
fetchDataPromise();使用 async/await:
javascript
const axios = require('axios');
// async/await 版本
async function fetchDataAsyncAwait() {
console.log('开始请求数据');
try {
const response = await axios.get('https://api.example.com/data');
console.log('请求成功:', response.data);
} catch (error) {
console.error('请求失败:', error);
}
console.log('请求操作已启动');
}
fetchDataAsyncAwait();6.5 新手易错点:异步代码执行顺序误区
误区1:认为异步代码会立即执行
javascript
console.log('1');
setTimeout(() => {
console.log('2');
}, 0);
console.log('3');
// 输出:1 3 2误区2:在异步函数外使用 await
javascript
// 错误:在异步函数外使用 await
const data = await fs.readFile('example.txt', 'utf8');
console.log(data);
// 正确:在异步函数内使用 await
async function readFile() {
const data = await fs.readFile('example.txt', 'utf8');
console.log(data);
}
readFile();误区3:忘记处理 Promise 错误
javascript
// 错误:忘记处理错误
fs.readFile('example.txt', 'utf8')
.then(data => {
console.log(data);
});
// 正确:处理错误
fs.readFile('example.txt', 'utf8')
.then(data => {
console.log(data);
})
.catch(err => {
console.error(err);
});误区4:在循环中使用异步操作
javascript
// 错误:循环中的异步操作
const files = ['file1.txt', 'file2.txt', 'file3.txt'];
for (let i = 0; i < files.length; i++) {
fs.readFile(files[i], 'utf8', (err, data) => {
if (err) {
console.error(err);
return;
}
console.log(`文件 ${files[i]} 内容:`, data);
});
}
// 正确:使用 async/await 和 for...of 循环
async function readFiles() {
const files = ['file1.txt', 'file2.txt', 'file3.txt'];
for (const file of files) {
try {
const data = await fs.readFile(file, 'utf8');
console.log(`文件 ${file} 内容:`, data);
} catch (err) {
console.error(err);
}
}
}
readFiles();小结
- 同步编程会阻塞程序执行,异步编程不会
- Node.js 中异步编程有三种方式:回调函数、Promise、async/await
- 回调地狱是多层嵌套的回调函数,使代码难以维护
- Promise 和 async/await 是解决回调地狱的有效方法
- async/await 是最简洁、最易读的异步编程方式
- 新手需要注意异步代码的执行顺序,避免常见误区
现在,你已经了解了 Node.js 的异步编程,接下来让我们学习 HTTP 服务器开发。
