1. 回调函数
- 解决嵌套问题:在早期JavaScript中,异步操作常以回调函数形式实现。例如读取文件,
fs.readFile
接受文件名、编码和回调函数作为参数。但当多个异步操作相互依赖时,会导致回调地狱。
const fs = require('fs');
fs.readFile('file1.txt', 'utf8', (err, data1) => {
if (err) {
console.error(err);
return;
}
fs.readFile('file2.txt', 'utf8', (err, data2) => {
if (err) {
console.error(err);
return;
}
console.log(data1 + data2);
});
});
- 错误处理:错误通过回调函数的第一个参数传递。如上述代码,每次异步操作都需在回调中检查
err
。
- 底层设计考量:简单直接,符合JavaScript单线程、事件驱动的模型。但随着异步操作增多,代码可读性和维护性变差。
2. Promise
- 解决嵌套问题:Promise将异步操作封装成一个Promise对象,该对象有
pending
、fulfilled
、rejected
三种状态。通过.then()
方法链式调用解决嵌套问题。
const fs = require('fs');
const util = require('util');
const readFile = util.promisify(fs.readFile);
readFile('file1.txt', 'utf8')
.then(data1 => {
return readFile('file2.txt', 'utf8').then(data2 => {
return data1 + data2;
});
})
.then(result => {
console.log(result);
})
.catch(err => {
console.error(err);
});
- 错误处理:使用
.catch()
捕获整个Promise链中的错误,无需在每个异步操作回调中单独处理。
- 底层设计考量:利用状态机机制,将异步操作的成功和失败处理分离,使代码逻辑更清晰,符合链式调用的设计模式,便于管理复杂异步流程。
3. async/await
- 解决嵌套问题:
async
函数返回一个Promise对象,await
只能在async
函数内部使用,它暂停async
函数执行,等待Promise解决。这样代码看起来像同步的,极大简化了异步操作嵌套。
const fs = require('fs');
const util = require('util');
const readFile = util.promisify(fs.readFile);
async function readFiles() {
try {
const data1 = await readFile('file1.txt', 'utf8');
const data2 = await readFile('file2.txt', 'utf8');
console.log(data1 + data2);
} catch (err) {
console.error(err);
}
}
readFiles();
- 错误处理:使用
try...catch
块捕获错误,比Promise的.catch()
更直观,符合同步代码的错误处理习惯。
- 底层设计考量:基于Promise构建,语法糖形式使异步代码更接近同步代码风格,提高代码可读性和可维护性,同时保留了Promise的异步特性和错误处理机制。