「JS硬气功👊」从回调函数,迈一步两步走通JS异步(JS异步初步指南)

208 阅读4分钟

Hi!这里是JustHappy,相信大家在平时写代码的时候大多数异步操作都是通过调用相关的库来完成的,比如说axios、fetch,这大概率会导致我们对原始的异步编程记忆模糊,所以今天我们来回顾一下,就让我们从回调函数开始吧!

image.png

聊聊“回调函数”

什么是回调函数?

回调函数是一种编程模式,它允许我们将这个函数作为参数传递给另一个函数,并在某个操作完成时调用这个函数。

就像下面这样......(我们这里使用setTimeout去模拟异步的情况)

function fetchData(callback){
    setTimeout(() => {
        console.log("数据获取完成");
        callback("我*你*");
    }, 2000); // 模拟异步操作,2秒后完成
}

function showData(data){
    console.log("数据是:",data)
}

fetchData(showData)

“回调地狱”

这其实是一个诙谐的说法,“回调地狱”本质上指的是指在使用回调函数处理异步操作时,由于嵌套过多的回调函数,导致代码结构变得复杂、难以阅读和维护的现象,就像下面这样

doSomething(function(result1) {
  ......
  doSomethingElse(result1, function(result2) {
      ......
        doAnotherThing(result2, function(result3) {
            ......
          // 继续嵌套
        });
  });
});

所以为了解决回调地狱,我们迎来了Promise

Promise

Promise 是一种用于处理异步操作的对象,它表示一个尚未完成但预计将来会完成的操作。它的基本使用就像下面这样

function fetchData() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log("数据获取完成");
            resolve("我*你*"); // 模拟成功返回的数据
        }, 2000); // 模拟异步操作,2秒后完成
    });
}

fetchData()
    .then(data => {
        console.log("数据是:", data);
    })
    .catch(error => {
        console.error("发生错误:", error);
    });

如果是使用Promise实现一下上面那个嵌套会是怎么样的呢?

首先我们要处理的函数将会变的不再嵌套,就像下面这样

// 模拟异步操作的函数,返回一个 Promise
function doSomething() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log("第一步完成");
            resolve("result1"); // 模拟返回的结果
        }, 1000);
    });
}

function doSomethingElse(result1) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log("第二步完成,输入:", result1);
            resolve("result2"); // 模拟返回的结果
        }, 1000);
    });
}

function doAnotherThing(result2) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log("第三步完成,输入:", result2);
            resolve("result3"); // 模拟返回的结果
        }, 1000);
    });
}

ok,让我们来调用一下,这里我们使用Promise的链式调用就行了

// 使用 Promise 链式调用
doSomething()
    .then(result1 => {
        console.log("第一步的结果:", result1);
        return doSomethingElse(result1); // 将结果传递给下一个异步操作
    })
    .then(result2 => {
        console.log("第二步的结果:", result2);
        return doAnotherThing(result2); // 将结果传递给下一个异步操作
    })
    .then(result3 => {
        console.log("第三步的结果:", result3);
        console.log("所有异步操作完成!");
    })
    .catch(error => {
        console.error("发生错误:", error);
    });

可以看到,使用Promise使得我们的代码不再嵌套那么深,以至于我们产生繁重的心智负担

但是!这就是最优雅的解决方案吗?我们来看看 async / await

async / await

其实async / await只是我们Promise的一个语法糖,你会发现其实我们在使用Promise的时候本质上还是以异步代码的编写方式,链式调用这样的......

但是我们习惯于编写同步的代码,所以我们需要一个“转换器”,这就是 async / await

// 使用 async/await 实现异步操作
async function executeAsyncOperations() {
    try {
        const result1 = await doSomething(); // 等待第一步完成
        console.log("第一步的结果:", result1);

        const result2 = await doSomethingElse(result1); // 等待第二步完成
        console.log("第二步的结果:", result2);

        const result3 = await doAnotherThing(result2); // 等待第三步完成
        console.log("第三步的结果:", result3);

        console.log("所有异步操作完成!"); 
    } catch (error) {
        console.error("发生错误:", error);
    }
}

executeAsyncOperations();

是吧,其实我们实现了使用“同步”的方式编写“异步”的代码,我们只要先编写一个同步函数,再在前面加上async,在后面加上await就OK了

总结

我们引入 Promise 是为了解决回调地狱的问题。在传统的回调模式下,嵌套的回调函数会导致代码难以阅读和维护。Promise 通过链式调用 .then().catch(),使异步操作更加清晰、易于管理。然而,Promise 的链式调用仍然不够直观。于是,async/await 出现了。它基于 Promise,通过 await 关键字让异步代码的写法接近同步代码,逻辑更直观,错误处理更自然,进一步提升了代码的可读性和可维护性。