面试题答案
一键面试回调地狱的表现形式
在Node.js中,假设我们有一系列需要顺序执行的异步操作,例如读取文件、处理文件内容、写入新文件。传统使用回调函数实现时,代码可能如下:
const fs = require('fs');
fs.readFile('input.txt', 'utf8', (err, data) => {
if (err) {
console.error(err);
return;
}
const processedData = data.toUpperCase();
fs.writeFile('output.txt', processedData, (err) => {
if (err) {
console.error(err);
return;
}
console.log('File written successfully');
});
});
当异步操作进一步增多,例如再增加读取另一个文件并处理写入的操作,代码会变成多层嵌套,如下:
const fs = require('fs');
fs.readFile('input1.txt', 'utf8', (err, data1) => {
if (err) {
console.error(err);
return;
}
const processedData1 = data1.toUpperCase();
fs.writeFile('output1.txt', processedData1, (err) => {
if (err) {
console.error(err);
return;
}
fs.readFile('input2.txt', 'utf8', (err, data2) => {
if (err) {
console.error(err);
return;
}
const processedData2 = data2.toLowerCase();
fs.writeFile('output2.txt', processedData2, (err) => {
if (err) {
console.error(err);
return;
}
console.log('All operations completed successfully');
});
});
});
});
可以看到,随着异步操作的增加,代码变得越来越难以阅读和维护,这就是回调地狱的表现形式。
解决方案及其原理
- Promise
- 原理:Promise是一个代表异步操作最终完成(或失败)及其结果值的对象。它有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。一旦状态改变,就不会再变,这个特性使得异步操作的流程更加可控。
- 示例:
const fs = require('fs');
const util = require('util');
const readFilePromise = util.promisify(fs.readFile);
const writeFilePromise = util.promisify(fs.writeFile);
readFilePromise('input.txt', 'utf8')
.then(data => {
const processedData = data.toUpperCase();
return writeFilePromise('output.txt', processedData);
})
.then(() => {
console.log('File written successfully');
})
.catch(err => {
console.error(err);
});
这里util.promisify
将Node.js的基于回调的函数转换为返回Promise的函数。通过.then
链式调用,使得异步操作顺序执行,代码更加清晰,避免了回调地狱。
- async/await
- 原理:
async
函数是一种异步函数,它返回一个Promise对象。await
只能在async
函数内部使用,它会暂停async
函数的执行,等待Promise被解决(resolved)或被拒绝(rejected),然后恢复async
函数的执行并返回解决的值或抛出被拒绝的原因。 - 示例:
- 原理:
const fs = require('fs');
const util = require('util');
const readFilePromise = util.promisify(fs.readFile);
const writeFilePromise = util.promisify(fs.writeFile);
async function processFiles() {
try {
const data = await readFilePromise('input.txt', 'utf8');
const processedData = data.toUpperCase();
await writeFilePromise('output.txt', processedData);
console.log('File written successfully');
} catch (err) {
console.error(err);
}
}
processFiles();
async/await
语法使得异步代码看起来像同步代码,极大地提高了代码的可读性和可维护性,有效地解决了回调地狱问题。