从回调地狱到Promise:JS异步编程的进化🔄

313 阅读3分钟

这几天学习面试题,听说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引入了PromisePromise是一种更优雅的异步编程解决方案,它可以将嵌套的回调函数转换为链式调用,使代码更加清晰。

首先,我们可以将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对象,如果读取文件成功,Promiseresolve文件内容;如果失败,Promisereject错误。

接下来,我们可以使用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会暂停函数的执行,直到Promiseresolvereject。这样,代码看起来就像同步代码一样,非常直观。

总结

从回调函数到Promise,再到async/await,JavaScript的异步编程方式在不断进化。Promise解决了回调地狱的问题,而async/await则进一步简化了异步代码的书写。通过这些工具,我们可以写出更加清晰、易维护的异步代码。

无论是Promise还是async/await,它们都帮助我们更好地处理异步操作,避免了回调地狱的困扰。在实际开发中,我们可以根据场景选择合适的方式来编写异步代码,提升代码的可读性和可维护性。