这几天学习面试题,听说promise必考,吓得我赶紧写篇文章压压惊
在JavaScript中,处理异步操作是一个常见的需求。比如,读取文件、发送网络请求、定时任务等都需要异步处理。早期的JavaScript开发者通常使用回调函数来处理这些异步操作,但这种方式很容易导致“回调地狱”,代码的可读性和可维护性大大降低。
什么是回调地狱?
回调地狱是指多个嵌套的回调函数,代码结构层层嵌套,难以阅读和维护。比如下面这段代码:
fs.readFile('./promise_all/a.txt', (err, data) => {
if(err){
return console.log(err);
}
res.push(data.toString());
fs.readFile('./promise_all/b.txt', (err, data) => {
if(err){
return console.log(err);
}
res.push(data.toString());
fs.readFile('./promise_all/c.txt', (err, data) => {
if(err){
return console.log(err);
}
res.push(data.toString());
console.log(res);
})
})
})
这段代码的目的是依次读取三个文件的内容,并将它们存储在一个数组中。虽然功能实现了,但代码的可读性非常差,嵌套层次深,错误处理也分散在各个回调函数中。
Promise的引入
为了解决回调地狱的问题,ES6引入了Promise。Promise是一种更优雅的异步编程解决方案,它可以将嵌套的回调函数转换为链式调用,使代码更加清晰。
首先,我们可以将fs.readFile封装成一个返回Promise的函数:
const readFilePromise = (path) => {
return new Promise((resolve, reject) => {
fs.readFile(path, (err, data) => {
if(err){
reject(err);return;
}else{
resolve(data.toString());
}
})
})
}
这个函数返回一个Promise对象,如果读取文件成功,Promise会resolve文件内容;如果失败,Promise会reject错误。
接下来,我们可以使用Promise的链式调用来依次读取文件:
readFilePromise('./promise_all/a.txt')
.then((data) => {
res.push(data);
return readFilePromise('./promise_all/b.txt');
})
.then((data) => {
res.push(data);
return readFilePromise('./promise_all/c.txt');
})
.then((data) => {
res.push(data);
})
.catch((err) => {
console.log(err);
})
.finally(() => {
console.log(res);
})
这段代码通过then方法将多个异步操作串联起来,每个then方法都返回一个新的Promise,从而避免了回调地狱。catch方法用于捕获错误,finally方法则无论成功或失败都会执行。
更进一步:async/await
虽然Promise已经大大改善了异步代码的可读性,但ES8引入了async/await语法,使得异步代码看起来更像同步代码,进一步提升了代码的可读性。
我们可以将上面的Promise链式调用改写为async/await形式:
(async () => {
try {
const data = await readFilePromise('./promise_all/a.txt');
res.push(data);
const data2 = await readFilePromise('./promise_all/b.txt');
res.push(data2);
const data3 = await readFilePromise('./promise_all/c.txt');
res.push(data3);
console.log(res);
} catch (err) {
console.log(err);
}
})();
这段代码使用了async函数和await关键字。await会暂停函数的执行,直到Promise被resolve或reject。这样,代码看起来就像同步代码一样,非常直观。
总结
从回调函数到Promise,再到async/await,JavaScript的异步编程方式在不断进化。Promise解决了回调地狱的问题,而async/await则进一步简化了异步代码的书写。通过这些工具,我们可以写出更加清晰、易维护的异步代码。
无论是Promise还是async/await,它们都帮助我们更好地处理异步操作,避免了回调地狱的困扰。在实际开发中,我们可以根据场景选择合适的方式来编写异步代码,提升代码的可读性和可维护性。