javascript异步编程的演进史 | 豆包MarsCode AI刷题

33 阅读3分钟

同步代码的困扰

默认状态下,JavaScript写的是同步代码,上面的代码执行完之后才会轮到下面代码的执行,假如你写了一个生成n个素数的网页,当你点击按钮之后,一个函数运行,在同步的状态下,直到你的函数运行完毕,用户不能操作这期间网页上任何东西。

但是,我们希望实现一种效果:

  • 通过调用一个函数来启动一个长期运行的操作
  • 让函数开始操作并立即返回,这样我们的程序就可以保持对其他事件做出反应的能力
  • 当操作最终完成时,通知我们操作的结果。

回调地狱

历史上,JavaScript曾使用回调(callback)的思路来实现

回调函数,回调函数基于事件模型(event model),此处的事件一般是某个对象的状态变化,当监听器监听到变化时,触发的就是回调函数,这种异步操作很容易导致写一大堆一连串的回调函数,这就是经典的“回调地狱”

所以,使用回调的异步成为了历史,JavaScript使用了Promise来处理异步

// 回调地狱示例
generatePrimesCallback(100, (primes1) => {
    console.log('First batch:', primes1);
    generatePrimesCallback(200, (primes2) => {
        console.log('Second batch:', primes2);
        generatePrimesCallback(300, (primes3) => {
            console.log('Third batch:', primes3);
            // 继续嵌套...
        });
    });
});

Promise与then

Promise 是现代 JavaScript 中异步编程的基础。它是一个由异步函数返回的对象,可以指示操作当前所处的状态。在 Promise 返回给调用者的时候,操作往往还没有完成,但 Promise 对象提供了方法来处理操作最终的成功或失败。

Promise 是现代 JavaScript 异步编程的基石,它提供了更优雅的方式来处理异步操作:

  • Promise 是一个表示异步操作的对象
  • 它有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败)
  • Promise 的 .then() 方法可以链式调用,避免了回调地狱
  • 提供了统一的错误处理机制(.catch()

Promise是一个类型的对象,一般由fetch返回,在Promise之后,往往还跟着大量的then,乍看之下,好像Promise只是把回调地狱变成了一连串的then,但then的绝妙之处在于它返回的也是promise,你实际上写的是一个类似管道的操作,而不是回调地狱

// 回调地狱示例
generatePrimesPromise(100)
    .then(primes1 => {
        console.log('First batch:', primes1);
        return generatePrimesPromise(200);
    })
    .then(primes2 => {
        console.log('Second batch:', primes2);
        return generatePrimesPromise(300);
    })
    .then(primes3 => {
        console.log('Third batch:', primes3);
    })
    .catch(error => {
        console.error('Error:', error);
    });

现代化async与await

但前端的追求不仅于此,async和await让异步操作更进一步简化,它允许使用者像编写同步代码一样编写异步代码,只需要注意async和await的传染性即可

Async/Await 其实是是建立在 Promise 之上的语法糖,它让异步代码更接近同步代码的写法:

  • async 关键字声明一个异步函数,该函数总是返回一个 Promise
  • await 关键字等待一个 Promise 完成并返回结果
  • 具有传染性:使用 await 的函数必须标记为 async
  • 使用 try/catch 进行错误处理,更符合直觉
// async & await
async function fetchUserData(userId) {
    try {
        const response = await fetch(`https://api.example.com/users/${userId}`);
        const userData = await response.json();
        return userData;
    } catch (error) {
        console.error('Failed to fetch user data:', error);
        throw error;
    }
}