作为初级开发人员,我会喜欢 Promises 指南

2025-05-25

作为初级开发人员,我会喜欢 Promises 指南

你还记得第一次遇到“Promise {pending}”时的困惑吗?你只是想试试 .then 是否能神奇地运行它?这篇文章就是为你而写的,继上一篇关于NPM的文章之后,我将带你一起重新学习 JS 生态系统的基础知识。

本文主要涵盖 Promises 的理解(如何编写和识别),以及 async/await 的深入讲解。您可以参考下方目录,直接跳转到其他部分。

免责声明:我们不会看到 Promises 的“隐藏”部分。隐藏是指 JavaScript 引擎如何在调用堆栈中处理它们。

目录

必要的背景信息

MDN Web 文档对 Promise 的定义如下:
Promise 是 Promise 创建时不一定已知的值的代理。
它允许你将处理程序与异步操作的最终成功值或失败原因关联起来。

如果您和我一样不清楚,请继续关注,我们会把一切都弄清楚的,我保证😉。

那么,什么是 Promises?

现在我们已经把这个糟糕的笑话抛在脑后,让我们直接进入正题。

首先,介绍一下基础知识。JavaScript 是一种同步且单线程的语言。这意味着你的所有代码都将按照编写的顺序执行,并且只有一个调用堆栈。为了简单起见,我们假设 JS 是一种所有操作都按顺序进行的语言,无需任何外部插件。

承诺是一种异步执行某些代码片段的方法,这意味着它们将在后台执行,而其余的同步代码则完成。

我们为什么需要它们?

我们举一个现实生活中的例子,有两个服务员。简单来说,我们的服务员负责接单,然后把菜从厨房送到顾客手中。

我们的第一个服务员会是同步的(就像 JavaScript 的 Promise 从未存在过一样)。他会接单,然后把订单发给厨房,等待菜做好,最后上菜,如此循环往复。有点尴尬,效率低下。

我们的第二个服务员会异步处理。他会接受顾客的订单,然后将其发送给厨房。等菜做好了,他就会去做别的事情,等菜做好了再回来取菜。

这正是 JS 正在发生的事情。厨房会向服务员承诺,菜品会在未来的某个时间准备好。

如果承诺从未存在,那么所有需要外部 HTTP 调用的代码都会阻止其余代码执行,直到调用结束,就像我们的第一位服务员在订单之间被困在厨房一样。

具体来说,在网站上,你得等到所有内容加载完毕才能看到文字或形状,这会导致加载时间过长,或者页面看起来很奇怪。现在,没人愿意等待网站加载超过两三秒,对吧?

让我们深入研究代码

我如何使用它们?

现在,让我们编写第一个 Promise。一个非常基本的 Promise 如下所示:

new Promise((resolve, reject) => {
  // Your asynchronous code
});
Enter fullscreen mode Exit fullscreen mode

Promise 总是接受一个带有两个参数的函数:resolverejection。当 Promise 必须返回结果时,我们会使用结果调用 resolve 。如果发生了错误,比如缺少某些食材,导致整个 Promise 无法使用,我们必须取消订单并从客户那里获得其他结果,这时我们会调用 rejection。

// Your promise can be stored in a variable, or returned within a function
const preparingDishes = new Promise((resolve, reject) => {
  const isIngredientMissing = false;

  const dishes = [
    // Everything that the client ordered, it would be filled up as soon as one is ready
  ];

  // But if an ingredient is missing, immedialty call back the waiter to inform the clients
  if (isIngredientMissing) return reject('An ingredient is missing !');

  // We use setTimeout to mimic an HTTP call
  setTimeout(() => {
    // 10 seconds later, once the dishes are ready, send them to the waiter
    return resolve(dishes);
  }, 10000);
});
Enter fullscreen mode Exit fullscreen mode

那么, preparingDishes的值是多少

console.log(preparingDishes); // <---- What will appear in the console ?
Enter fullscreen mode Exit fullscreen mode

如果你已经熟悉 Promise,那么你的答案应该是“Promise {pending}”,没错。
如果你期待的是[ ],不用担心,我们会解决这个问题。

console.log(preparingDishes); // <---- Promise {pending}
Enter fullscreen mode Exit fullscreen mode

为什么?我们继续用服务员的例子,我们对厨房说:“嘿,我有一个新订单,请准备一下。” 我们没有预留足够的准备时间,所以厨房回答说:“还没准备好!我们还在准备中。” 承诺尚未完成

那么我们如何获取该值呢?

你还记得 MDN 的定义吗?它允许你关联处理程序,这里的关键字是 handlers。让我们回到之前的例子,但这次让我们让它真正发挥作用。

const preparingDishes = new Promise((resolve, reject) => {
  // See the code above
});

// Now that our promise is created, let's trigger it, and then read the results
preparingDishes
  .then((dishes) => {
    // dishes is a arbitrary name, usually it's called result

    /* Within this function, we can access the result of the promise. The parameter will be the value you gave to the resolve.
    You are guaranted that anything you put in here, will execute when the promise is fullfilled (succesfull) */
    callWaiter(dishes);
  })
  .catch((error) => {
    // In case an error happened, this handler will catch the return value inside your above reject or any error that could happen in your promise code
    if (error === 'An ingredient is missing !') {
      sendWaiterBacktoClients();
    }
  })
  .finally(() => {
    // This one will execute anything that you put inside, either the promise succeed or not

    // For example, wether the kitchen succeed preparing the dishes or not, they'll have to start the next one
    prepareNextDishes();
  });
Enter fullscreen mode Exit fullscreen mode

您现在肯定已经注意到了,.then.catch.finally是 MDN 中提到的处理程序。如上所述,每个处理程序会在不同的环境下执行。
请注意,附加所有处理程序并非强制性的,例如,您可以只附加一个 .then(但我不建议这样做),或者只附加一个 .then 和一个 .catch,这也是您大多数情况下会用到的。

太棒了!现在我们已经写好了第一个 Promise,并且正确使用了它,接下来我们来看最后一部分,我个人在这方面遇到了很多困难。

我如何识别我没有写的承诺?

假设你正在启动一个新项目,你必须熟悉代码库,但不确定哪些是异步的,哪些不是。或者更糟的是,你正在尝试如何在没有文档的情况下使用第三方库。

您有两种解决方案来判断特定代码段是否是承诺。

  • 如果您使用 VSC 作为文本编辑器,您可以将鼠标悬停在您感兴趣的代码段上。在出现的弹出窗口中,您可以分析其值。如果这是一个承诺,您将看到它:
  Promise<any>
Enter fullscreen mode Exit fullscreen mode

不要让“any”关键字引起怀疑,这是一些花哨的 Typescript,并且将被承诺返回的任何值替换。

  • 如果.then还在,那么它之前的变量 100% 就是一个承诺(除非你正在执行的代码已经损坏了😕)。

附加信息

正如我们所见,Promise 后面总是会跟着特定的处理程序。它们都应该与 Promise 一起使用,例如,将它们与字符串一起使用会导致错误,JS 会抱怨.then不是一个函数。

即使 Promise 很酷,嵌套 Promise 也会导致我们所说的回调地狱。想象一下,一段特定的代码是由一系列 Promise 组成的,你必须等待前面的 Promise 完成后才能访问该值,如此反复,最终导致类似以下可怕的事情:

gettingOrder.then((order) => {
  giveOrderToKitchen(order).then((dishes) => {
    serveClient(dishes).then(() => {
      startOver();
    });
  });
});
Enter fullscreen mode Exit fullscreen mode

我故意省略了所有的.catch代码,因为代码的可读性已经下降了。为了解决这个问题,我们有一个强大的工具,那就是async/await

异步/等待

首先,我想澄清一些我花了很长时间才理解的事情,async/await只不过是promises的语法糖

让我们重写我们的第一个承诺:

// Your function must be async to use the await keyword
async function waiterJob() {
  try {
    const dishes = await preparingDishes;
  } catch (error) {
    // Handle the error
  }
}
Enter fullscreen mode Exit fullscreen mode

所以,这里有一些变化,我们现在有一个带有关键字async 的函数、一个try/catch块和一个await关键字。即使我们还没明白这里发生了什么,但我们已经可以说它干净多了。

以下是使用 async/await 的规则:

  • await 关键字只能在异步函数中使用。

    • 替代了.then,您不能将它与 .then/.catch 结合使用。例如:
  // ☠️ Don't
  await preparingDishes.then((dishes) => {});

  // Don't do this kind of no sense either, it would work, but it's way more readable as a full async/await
  preparingDishes.then(async (dishes) => {
    await something();
  });
Enter fullscreen mode Exit fullscreen mode
  • 将函数设置为异步会强制其返回值成为 Promise。在我们的示例中,waiterJob 函数就是一个 Promise,因此调用它时也必须 await。
  • 使用 await 比 .then 要复杂一些。如果你愿意,也可以在字符串前使用 await,JS 不会像 .then 那样报错。请务必小心,不要在任何地方都使用它。
    • 在不需要的地方使用 await 本身不会导致错误,但它周围的异步函数可能会导致错误,因为如果处理不当,它可能会破坏您的应用程序。
  • 为了正确处理错误,你必须将 Promise 调用包裹在 try/catch 块中。我们之前的 .catch 代码将在这里处理,并且任何在 try 中中断的操作都将被捕获。

等一下,你不是说所有异步函数都必须被 await 吗?这简直是无休止的!

其实不然。在实际应用中,大多数情况下,你都会有一个向上的同步函数,但没有人会依赖它,比如初始化函数。

如果你没有,你可以编写我们所说的IIFE,它基本上是一个自调用函数,允许你执行以下操作:

// How can I achieve this ?

import prepareTable from './';

const table = await prepareTable; // <---- Error, it's not wrapped within an async function

// ----------------------------------------

import prepareTable from './';

(async function () {
  const table = await prepareTable; // 👍
})();
Enter fullscreen mode Exit fullscreen mode

这是我们最后一个例子用 async/await 重构后的样子

async function waiterJob() {
  try {
    const order = await gettingOrder();
    const dishes = await giveOrderToKitchen(order);
    await serveClient(dishes);
    startOver();
  } catch (error) {
    // Handle the error
  }
}
Enter fullscreen mode Exit fullscreen mode

到这里就结束了,我们已经了解了所有能帮助你开始使用 Promise 的知识。如果你是一位经验丰富的开发者,并且觉得还缺少什么,欢迎随时留言补充。

您可以在Othrys 网站上找到原始文章,也可以关注我的Twitter或在这里标记我来讨论这篇文章。

文章来源:https://dev.to/chandelieraxel/the-promises-guide-i-would-have-loved-as-a-junior-developper-3621
PREV
2022 年前端 Web 开发者路线图及资源
NEXT
Socket.io 入门