异步 JavaScript 终极指南

2025-06-07

异步 JavaScript 终极指南

黄色柯基犬

在成为 JavaScript 开发人员的道路上,您可能会遇到回调承诺异步/等待

JavaScript 本质上是一种同步或单线程语言。这意味着每个操作都是逐个执行的,并且每个操作都依赖于前一个操作的执行。

想象一下在红绿灯处等待的汽车。每辆车都必须等待前一辆车启动。🚗 🚕

const firstCar = 'first car';
const secondCar = 'second car';

console.log('Start ' + firstCar);
console.log('Start ' + secondCar);

Enter fullscreen mode Exit fullscreen mode

输出

Start first car
Start second car
Enter fullscreen mode Exit fullscreen mode

但如果第一辆车坏了怎么办?其他车都要等吗?谁有时间呢?

const firstCar = 'broken';
const secondCar = 'second car';

if (firstCar === "broken") {
  throw Error("The first car is broken. Everybody stop.");
}


console.log('Start ' + firstCar);
console.log('Start ' + secondCar);
Enter fullscreen mode Exit fullscreen mode

输出

Error: The first car is broken. Everybody stop.
Enter fullscreen mode Exit fullscreen mode

那么,每辆车都不要依赖于前一辆,不是更好吗?我们为什么要在意哪辆车坏了?如果我的车能开,为什么还要等别人发动?难道我不能绕过去吗?

这就是异步JavaScript 允许我们做的事情。它为我们创建了另一条“通道”。异步意味着,如果 JavaScript 必须等待某个操作完成,它会在等待期间执行剩余的代码。我们可以将操作移出主通道,按照它们自己的节奏执行,让它们各自独立地运行。那么,我们该如何实现呢?

通过使用回调承诺异步/等待

回调

回调函数是嵌套在另一个函数中作为参数的函数。它们可以用作同步或异步代码的一部分。

同步回调是在使用回调的高阶函数执行过程中执行的。

function startFirst(car, callback) {
  console.log("Start " + car);
  callback();
}

// callback function
function startSecond() {
  console.log("Start second car");
}

// passing function as an argument
startFirst("first car", startSecond);
Enter fullscreen mode Exit fullscreen mode

输出

Start first car
Start second car
Enter fullscreen mode Exit fullscreen mode

我们还可以将回调作为异步 Javascript 的一部分。

异步回调在执行完使用回调的高阶函数执行。如果我们的车坏了,我们会把它送到修理厂,之后就可以再次使用了。首先,我们需要等待一段时间来修好车,我们将使用setTimeout来模拟这个过程,然后我们就可以享受驾驶刚修好的车了。

function fixMyCar(car) {
  setTimeout(() => {
    console.log(`Fixing your ${car}.`);
  }, 1000);
}

function driveMyCar(car) {
  console.log(`Driving my "new" ${car}.`);
}

let myCar = "Yellow Corgi CC85803"; // no wonder it's broken

fixMyCar(myCar);
driveMyCar(myCar);

Enter fullscreen mode Exit fullscreen mode

输出

Driving my "new" Yellow Corgi CC85803.
Fixing your Yellow Corgi CC85803.
Enter fullscreen mode Exit fullscreen mode

Javascript 首先执行同步代码(在我们的例子中是调用 driveMyCar() 函数),然后在 1000 毫秒后记录 fixMyCarFunction() 的结果。

可是等等。如果车还没修好,我们怎么开车呢?

我们必须将 driveMyCar() 函数作为回调传递给 fixMyCar() 函数。这样,​​driveMyCar() 函数在汽车修好之前不会执行。

function fixMyCar(car, callback) {
  setTimeout(() => {
    console.log(`Fixing your ${car}.`);
    callback(car);
  }, 1000);
}

function driveMyCar(car) {
  console.log(`Driving my "new" ${car}.`);
}

let myCar = "Yellow Corgi CC85803";

fixMyCar(myCar, driveMyCar);

Enter fullscreen mode Exit fullscreen mode

输出

Fixing your Yellow Corgi CC85803.
Driving my "new" Yellow Corgi CC85803.
Enter fullscreen mode Exit fullscreen mode

好吧,这太棒了,我们的车修好了,现在我们可以开车了。

但如果我们的车修不好怎么办?这没什么好奇怪的,你见过吗?我们该如何处理错误?而且,如果每天要修好几辆车怎么办?

让我们看看它的实际效果。

function fixMyCar(car, success, failure) {
  setTimeout(() => {
    car ? success(car) : failure(car);
  }, 1000);
}

const car1 = "Yellow Corgi CC85803";
const car2 = "Red Peel Trident";
const car3 = "Blue Yugo GV";

fixMyCar(
  car1,
  function (car1) {
    console.log(`Fixed your ${car1}.`);
    fixMyCar(
      car2,
      function (car2) {
        console.log(`Fixed your ${car2}.`);
        fixMyCar(
          car3,
          function (car3) {
            console.log(`Fixed your ${car3}.`);
          },
          function (car3) {
            console.log(`Your ${car3} car can not be fixed.`);
          }
        );
      },
      function (car2) {
        console.log(`Your ${car2} car can not be fixed.`);
      }
    );
  },
  function (car1) {
    console.log(`Your ${car1} car can not be fixed.`);
  }
);

Enter fullscreen mode Exit fullscreen mode

输出

Fixed your Yellow Corgi CC85803.
Fixed your Red Peel Trident.
Fixed your Blue Yugo GV.
Enter fullscreen mode Exit fullscreen mode

是不是头晕目眩,想不明白?别担心,你不是一个人。这被称为回调地狱毁灭金字塔
是有原因的。🔥 另外,请注意,如果其中一辆车报废了,也就是说它无法识别(未定义),其他车甚至没有机会修好。这难道不令人难过吗?难道我们不应该拥有第二次机会吗?😊

function fixMyCar(car, success, failure) {
  setTimeout(() => {
    car ? success(car) : failure(car);
  }, 1000);
}

const car1 = "Yellow Corgi CC85803";
const car2 = undefined;
const car3 = "Blue Yugo GV";

fixMyCar(
  car1,
  function (car1) {
    console.log(`Fixing your ${car1}.`);
    fixMyCar(
      car2,
      function (car2) {
        console.log(`Fixing your ${car2}.`);
        fixMyCar(
          car3,
          function (car3) {
            console.log(`Fixing your ${car3}.`);
          },
          function (car3) {
            console.log(`Your ${car3} car can not be fixed.`);
          }
        );
      },
      function (car2) {
        console.log(`Your ${car2} car can not be fixed.`);
      }
    );
  },
  function (car1) {
    console.log(`Your ${car1} car can not be fixed.`);
  }
);

Enter fullscreen mode Exit fullscreen mode

输出

Fixing your Yellow Corgi CC85803.
Your undefined car can not be fixed.
Enter fullscreen mode Exit fullscreen mode

我们该如何避免这一切?Promise 来帮我们!

承诺

Promise 是一个对象,当异步操作的结果不能立即获得时,可以使用它来获取该结果。

由于 JavaScript 代码以非阻塞方式运行,因此当我们必须等待某些异步操作而不阻止其余代码的执行时,承诺就变得至关重要。

JavaScript 承诺是具有三种状态之一的对象。

待处理- 承诺仍未解决(您的汽车在机械师处)
已完成- 请求成功(汽车已修好)
已拒绝- 请求失败(汽车无法修复)

要在 JavaScript 中创建 Promise,请使用new关键字,并在构造函数中传递执行函数。该函数负责解决或拒绝该 Promise。

让我们想象一下这样的场景。如果我们的车修好了,我们就可以去度假了。我们可以去观光,然后拍些照片,发到社交媒体上,因为现在的酷孩子们都这么做。但如果我们的车修不好,我们就只能和我们的猫待在我们那间又小又暗又破的公寓里了。

让我们写下我们的步骤。

  1. 机械师承诺,她会修好我们的车🔧
  2. 修好车就意味着要去度假。🌴
  3. 到了那里我们就可以去观光了🌄
  4. 我们会拍一些照片📷
  5. 之后我们会将它们发布到社交媒体上📱

(我们再次使用 setTimeout 来模拟异步)

const mechanicsPromise = new Promise((resolve, reject) => {
  setTimeout(() => {
    const fixed = true;
    if (fixed) resolve("Car is fixed.");
    else reject("Car can not be fixed.");
  }, 2000);
});

console.log(mechanicsPromise);

Enter fullscreen mode Exit fullscreen mode

输出

Promise { <pending> }
Enter fullscreen mode Exit fullscreen mode

如果你检查控制台,你将会得到:

Promise { <pending> }

[[Prototype]]: Promise
[[PromiseState]]: "pending"
[[PromiseResult]]: undefined
Enter fullscreen mode Exit fullscreen mode

但是等等,PromiseResult 怎么会是 undefined 呢?你的机修工不是说过她会尽力修好你的车吗?不,你的机修工没有欺骗你。我们忘了消费 Promise 了。该怎么做呢?用.then().catch()方法。

const mechanicsPromise = new Promise((resolve, reject) => {
  setTimeout(() => {
    const fixed = true;
    if (fixed) resolve("Car is fixed.");
    else reject("Car can not be fixed. Go home to your cat.");
  }, 2000);
});

mechanicsPromise
  .then((message) => {
    console.log(`Success: ${message}`);
  })
  .catch((error) => {
    console.log(error);
  });

Enter fullscreen mode Exit fullscreen mode

输出

Success: Car is fixed.
Enter fullscreen mode Exit fullscreen mode

让我们在控制台中检查我们的 Promise 对象。

输出

[[Prototype]]: Promise
[[PromiseState]]: "fullfiled"
[[PromiseResult]]: Success: Car is fixed.
Enter fullscreen mode Exit fullscreen mode

从上面的代码块中可以看出,我们使用 .then() 来获取 resolve() 方法的结果,使用 .catch() 来获取 rejection() 方法的结果。

我们的车修好了,现在我们可以去度假并做我们计划的一切事情了。

.then() 方法返回一个新的 Promise,其值已解析为一个值,我们可以在返回的 Promise 上调用 .then() 方法,如下所示:

const mechanicsPromise = new Promise((resolve, reject) => {
  setTimeout(() => {
    const fixed = true;
    if (fixed) resolve("Car is fixed.");
    else reject("Car can not be fixed.");
  }, 2000);
});

mechanicsPromise
  .then((message) => {
    console.log(`Success: ${message}`);
    message = "Go sight seeing";
    return message;
  })
  .then((message) => {
    console.log(message);
    message = "Take some pictures";
    return message;
  })
  .then((message) => {
    console.log(message);
    message = "Posting pictures on social media";
    console.log(message);
  })
  .catch((error) => {
    console.log(error);
  })
  .finally(() => {
    console.log("Go home to your cat.");
  });

Enter fullscreen mode Exit fullscreen mode

输出

Success: Car is fixed.
Go sight seeing.
Take some pictures.
Posting pictures on social media.
Go home to your cat.
Enter fullscreen mode Exit fullscreen mode

如您所见,每次调用 .then() 方法后,我们都链接了另一个 .then() ,并将解析后的消息传递给上一个 .then()。
我们还添加了 .catch() 来捕获可能出现的任何错误。
无论我们去度假还是不去度假,我们肯定都要回家。
这就是.finally() 的作用,无论 Promise 是 fulfilled 还是 rejected,此方法都会执行。换句话说,finally() 方法在 Promise 完成时执行。

与使用回调相比,我们的代码看起来更美观一些。但我们可以使用一种名为“async/await”的特殊语法使其更加出色。它让我们能够以更舒适的方式使用 Promise。

异步/等待

Async/await 允许我们编写 Promise,但代码看起来是同步的,尽管实际上是异步的。在底层我们仍然在使用 Promise。Async/await 是一种语法糖,这意味着虽然它不会给我们的代码添加任何新功能,但使用起来更顺手。🍬

我不知道你是怎么想的,但我不亲眼看到它,是不会相信的。

const mechanicsPromise = new Promise((resolve, reject) => {
  setTimeout(() => {
    const fixed = true;
    if (fixed) resolve("Car is fixed.");
    else reject("Car can not be fixed.");
  }, 2000);
});

async function doMyThing() {
  let message = await mechanicsPromise;
  console.log(`Success: ${message}`);
  message = "Go sight seeing";
  console.log(message);
  message = "Take some pictures";
  console.log(message);
  message = "Posting pictures on social media";
  console.log(message);
  console.log("Go home to your cat.");
}

doMyThing()
Enter fullscreen mode Exit fullscreen mode

输出

Success: Car is fixed.
Go sight seeing.
Take some pictures.
Posting pictures on social media.
Go home to your cat.
Enter fullscreen mode Exit fullscreen mode

如你所见,await 关键字使函数暂停执行,并等待已解决的 Promise 后再继续执行。await 关键字只能在异步函数中使用。

嘿,但如果我的车坏了怎么办?我该如何使用这个新语法来处理错误?

别害怕。我们可以使用try/catch块。

const mechanicsPromise = new Promise((resolve, reject) => {
  setTimeout(() => {
    const fixed = false;
    if (fixed) resolve("Car is fixed.");
    else reject("Car can not be fixed.");
  }, 2000);
});

async function doMyThing() {
  try {
    let message = await mechanicsPromise;
    console.log(`Success: ${message}`);
    message = "Go sight seeing";
    console.log(message);
    message = "Take some pictures";
    console.log(message);
    message = "Posting pictures on social media";
    console.log(message);
    console.log("Go home to your cat.");
  } catch (error) {
    console.log(error);
  }
}

doMyThing();

Enter fullscreen mode Exit fullscreen mode

输出

Your car can not be fixed. 
Enter fullscreen mode Exit fullscreen mode

仅在等待操作时使用 try/catch 块。否则将无法捕获异常。

所以,即使你的车坏了,现在你必须乘坐公共汽车,至少你学习了异步 JavaScript。😄

文章来源:https://dev.to/veronikasimic_56/the-ultimate-guide-to-asnyc-javascript-3lg5
PREV
如何打造你的 Solana 狙击机器人 (5)💰🚀
NEXT
探索 F# 前端领域