头图

异步编程是现代 JavaScript 开发中一个重要方面,它使我们能够处理耗时的操作,而不会阻塞其他任务的执行。使用异步函数时,我们会遇到三个重要的关键字:awaitreturnreturn await。在本文中,我们将探讨这些关键字之间的差异,并讨论何时使用每个关键字。

在深入探讨细节之前,让我们先阐明一下异步函数的用途。异步函数是一种特殊类型的函数,可以使用 await 关键字。它允许我们以更加同步和可读的方式编写异步代码,从而更容易处理 Promise 和执行非阻塞操作。当调用异步函数时,它会返回一个 Promise,该 Promise 解析为函数的最终结果。

现在,让我们探讨一下 awaitreturn、 和 return await 在异步函数上下文中的差异。

让我们从这个异步函数开始:

async function waitAndMaybeReject() {
  // 等待 1s 
  await new Promise(r => setTimeout(r, 1000));
  // 掷硬币
  const isHeads = Boolean(Math.round(Math.random()));
  
  if(isHeads) return 'yay';
  throw Error('Boo!');
}

它会返回一个等待一秒的 Promise,然后 50% 的几率以 "yay" 表示,或以错误拒绝,让我们以几种微妙的方式来使用它。

只是调用

让我们先来看一下,当我们简单地调用另一个异步函数而不正确处理返回的 Promise 时,异步函数的行为。请看下面的示例:

async function foo() {
  try {
    waitAndMaybeReject();
  } catch(e) {
    return 'caught';
  }
}

在这里,如果直接调用 foo,异步函数 foo 返回的 Promise 将始终以 undefined 表示,而无需等待函数 waitAndMaybeReject

因为我们没有 await 或者 return 异步函数 waitAndMaybeReject() 的结果,因此我们对它没有作出任何反应,像这样的代码通常都是错误的。

Await

关键字 await 在异步代码中起着至关重要的作用,它允许我们暂停异步函数的执行,直到承诺得到解决或拒绝,让我们看看它与仅调用 async 函数有何不同。

await 的本质:

  • 异步代码同步:await 通过阻塞执行,直到等待的 Promise 被解析或拒绝,简化了异步代码的使用。
  • 增强的可读性:它消除了深度嵌套回调或 then() 长链的需要,从而极大地提高了代码的可读性。
async function foo() {
  try {
    await waitAndMaybeReject();
    } catch(e) {
    return 'caught';
  }
}

在这里,如果调用 foo,返回的 Promise 总是会等待一秒,然后以 undefined 或以 "caught" 表示 fulfill

因为我们 await waitAndMaybeReject() 的结果,所以它 rejection 时,将变成错误抛出,我们的 catch 代码块也将执行。但是,如果 waitAndMaybeReject() 执行完毕,我们不会对值做任何处理。

Return

async function foo() {
  try {
    return waitAndMaybeReject();
  } catch(e) {
    return 'caught';
  }
}

在这里,如果你调用 foo,返回的 Promise 将始终等待一秒,然后要么以 "yay" 表示 fulfill,要么以 Error('Boo!') 表示 reject

由于通过 return waitAndMaybeReject,我们延迟了其结果,因此我们的 catch 代码块永远不会运行。

Return await

try/catch 块中,你需要的是 return await

retrun await 的本质:

  • 一致的值:return await 可确保函数始终一致的返回 Promise 的解析值,即使在没有严格必要的情况下也是如此,从而确保返回数据类型的一致性。
  • 控制流清晰:在有条件逻辑的情况下,return await 可以提供更清晰的控制流,从而更容易跟踪代码的执行路径。
async function foo() {
  try {
    return await waitAndMaybeReject();
  } catch(e) {
    return 'caught';
  }
}

在这里,如果调用 foo,将始终等待一秒后返回 Promise,然后以 "yay" 或者以 "caught" 表示 fulfill

因为我们等待 waitAndMaybeReject() 的结果,所以它的 rejection 将变成抛出的 throw,我们的 catch 代码块将执行。如果 waitAndMaybeReject() 执行完毕,我们将返回其结果。

如果上述内容看起来令人困惑,那么将其视为两个独立的步骤可能会更容易理解:

async function foo() {
  try {
    // 等待 waitAndMaybeReject() 的结果结算,
        // 并将已完成的值分配给 fulfilledValue:
    const fulfillValue = await waitAndMaybeReject();
    // 如果 waitAndMaybeReject() 的结果被拒绝,我们的代码就会抛出,然后跳到 catch 块。
    // 否则,此块将继续运行:
    return fulfillValue;
  } catch(e) {
    return 'caught';
  }
}

注意:在 try/catch 块之外,return await 是多余的,ESLint 甚至有一条规则来检测它,但它允许在 try/catch 中使用。

参考:


破晓L
2.1k 声望3.6k 粉丝

智慧之子 总以智慧为是