我保证这是 Async / Await 的实用指南

2025-06-09

我保证这是 Async / Await 的实用指南

在 ES8 中,我们找到了另一种比回调更易读的异步代码编写方式,即 Async / Await。在 ES6 中,我们已经有了 Promises。要理解 Async / Await,首先必须理解 Promises。

承诺

const resolveAfter2Seconds = () => {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve();
    }, 2000);
  });
}

resolveAfter2Seconds()
    .then(() => { console.log('resolved') })        // this gets executed 
    .catch(() => { console.log('some error') });    // this does not get executed

const rejectAfter2Seconds = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject();
    }, 2000);
  });
}

rejectAfter2Seconds()
    .then(() => { console.log('resolved') })        // this does not get executed
    .catch(() => { console.log('some error') });    // this gets executed 

resolveAfter2Seconds 函数将返回一个新的 Promise。每个 Promise 都有一个状态。初始状态为pending。之后,它可以变为fulfilledrejected。当状态为 时,fulfilled它会将 resolve 中的值传递给 then 函数,然后你可以对其进行任何操作。如果状态变为rejectedthen ,它将运行 catch() 函数。我希望现在你对 Promise 的基本概念已经清楚了。

问题

给出以下代码:

const resolveAfterXSeconds = (ms) => {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(ms);
    }, ms);
  });
}

resolveAfterXSeconds(2000)
    .then((ms) => { console.log(`resolved after ${ms}`) });
resolveAfterXSeconds(1000)
    .then((ms) => { console.log(`resolved after ${ms}`) });

这段代码大约会在 2 秒还是 4 秒内完成?什么时候我们会看到 console.log() ?那么这段代码是顺序执行、并发执行还是并行执行?

回答

这段代码是真正并行的。它会执行两个函数,然后返回第二个函数调用,因为超时只有 1000 毫秒;然后返回第一个函数调用,因为这里的超时是 2000 毫秒。所以你必须想想这是否真的是你想要的。也许这些函数调用相互依赖!所以这并非你真正想要的。

我所见过的一个可以实现这一目标的解决方案如下:

resolveAfterXSeconds(2000)
  .then((ms) => { 
    console.log('promise in the first then');

    resolveAfterXSeconds(1000).then((ms) => { console.log(`resolved after ${ms}`) })

    return ms;
  }).then((ms) => { console.log(`resolved after ${ms}`) });

我们首先以 2000 为参数调用函数,一旦解析完成,我们立即以 1000 为参数调用函数,然后返回第一个函数的毫秒数。返回值相当于 Promise.resolve(),这就是它在这里有效的原因。虽然这可以按顺序运行,但可读性不强,让我想起了我们想要避免的回调地狱。

但是 Promise.all() 怎么样?让我们看一个例子:

Promise.all([resolveAfterXSeconds(2000), resolveAfterXSeconds(1000)]).then((ms) => {
  console.log(`resolved after ${ms[0]}`);
  console.log(`resolved after ${ms[1]}`);
});

这段代码之所以是并发的,是因为 Promise.all() 会创建一个单独的 Promise,当它所依赖的所有 Promise 都解析完毕后,该 Promise 才会被解析。因此,两个 resolveAfterXSeconds 函数会被同时调用,但 then() 函数会在所有 Promise 都解析完毕后调用。之后,你会收到一个包含已解析 Promise 的数组。数组中每个解析值的顺序与 Promise 传递给 Promise.all() 函数的顺序相同。如果你有 2 个 API 调用,这种模式就很好用。例如,一个用于用户数据,一个用于位置信息,然后你可以将它们组合成一个对象。

我们需要所有这些信息来更好地理解 Async / Await!

异步/等待

让我们终于可以开始讨论 Async / Await 了!首先要说的是:Async / Await 并不能完全取代 Promises。Async / Await 通常更容易阅读,但也容易被误解。我们的第一个例子:

resolveAfterXSeconds = (ms) => {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(ms);
    }, ms);
  });
}


start = async () => {
  const first = await resolveAfterXSeconds(2000);
  console.log(first);
  const second = await resolveAfterXSeconds(1000);
  console.log(second);
}
start();

因此,我们仍在使用旧的 resolveAfterXSeconds 函数,这里没有任何变化。现在我们创建一个名为 start 的新函数,这里是箭头函数之前的 async 的第一个新东西。只有 async () => {} 才会返回一个函数。调用此函数将返回一个承诺。这里需要记住的重要一点是,如果承诺只是返回某些内容,它将立即实现。在下一行,我们也有一些新的东西。await 告诉 javascript 它必须在这里等待,直到右侧的承诺解决或拒绝,直到这个函数暂停。在我们的示例中,第一次调用 resolveAfterXSeconds 函数将花费 2 秒,然后它将运行 console.log,然后运行第二个 resolveAfterXSeconds 函数。因此,运行我们的启动函数大约需要 3 秒钟。最后,我们得到了我们想要的!在 javascript 中顺序运行的异步代码!

由此我们可以看出,Async/await 与 promise.then 是不一样的!这一点在编码时务必牢记。你必须使用正确的工具来完成正确的工作。

Async / Await 也可以像 promise.all 一样以并发方式使用。

resolveAfterXSeconds = (ms) => {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(ms);
    }, ms);
  });
}

concurrentStart = async () => {

  const first = resolveAfterXSeconds(2000);
  const second = resolveAfterXSeconds(1000);

  console.log(await first);
  console.log(await second);
}

唯一改变的是 await 现在在 console.log() 本身中。为什么现在是并发的?因为 first 和 second 都已经启动了,现在我们只是在等待它们完成,记住 async 会创建一个 Promise。如果你回想一下 Promise.all() 的例子,你会发现这个例子和这个完全一样。

让我们实践一下

获取 API

让我们看一下 fetch API。fetch(URL) 会返回一个新的 Promise,所以我们可以等待它,但现在我们正在处理网络函数,我们不知道它们是否会被解析,或者只是被拒绝了。所以我们需要处理错误。

fetchUserNames = async (endpoint) => {
  try {
    const response = await fetch(endpoint);
    let data = await response.json();
    return data.map(user => user.username);
  } catch (error) {
    throw new Error(error);
  }
}

start = async () => {
  userNames = await fetchUserNames('https://jsonplaceholder.typicode.com/users');
  console.log(userNames);
  fetchUserNames('https://wrong.url').catch(error => console.log(error));
}

start();

您可以在 Async / Await 函数中使用 Try / Catch 以获得更好的错误处理。附带说明一下:nodejs 将退出未捕获错误的进程!您可以将这里的返回值视为承诺中的 resolve 和 throw 拒绝。然后我们使用 fetch API 来获取数据。如您所见,fetch() 调用返回一个承诺。因为我们知道我们得到的是一个 JSON,所以我们在响应上调用 .json(),然后它本身再次为我们返回一个承诺,这就是为什么我们也需要这里 await 的原因。然后我们只提取用户名并返回新创建的数组。我们的启动函数需要是异步的,因为 await 只能在异步函数中调用。我故意在这里混合使用 await 和 promise,向您展示您可以同时使用它们!

koajs 新的 expressjs

app.get("/", async (request, response, next) => {
  try {
    const finalResult = await database.getStuff();
    response.json(finalResult);
  } catch (error) {
    next(error);
  }
});

如果你曾经使用过 expressjs,你就会明白这里发生了什么。koajs 与 expressjs 出自同一批开发人员之手,但从头开始构建,充分利用了 ES6+ 的特性。此外,它还会在必要的情况下使用 Promise。在本例中,我们在“/”路由上处理 HTTP GET 请求。正如你所见,此路由可以异步执行。然后,我们可以在箭头函数中执行任何操作。在这个例子中,你可以想象我们正在调用数据库获取数据,然后将其返回给客户端。

每 X 秒运行一次函数

const waitFor = (ms) => new Promise(r => setTimeout(r, ms));

const start = async () => {
  try {
    console.log("start");
    c = 1;
    while(c < 10){
      console.log('waiting!');
      await waitFor(2000)
      console.log('start some function');
      await runDBBackup();
      if(c === 3){
        throw new Error('An error happend');
      }
      c++
    }
    console.log('end');
  } catch (error) {
    console.log(`error: #{error}`);
  }
}

start();

好的,以上就是我们学到的知识。首先,我们需要将 setTimeout 包装在一个 Promise 中,它将在 X 秒后解析。就是这样!它不做任何其他事情。它只是暂停执行。然后我们创建启动函数。在本例中,我故意让它在运行 3 次后失败。这就是为什么我们有 c 变量。然后我们将进入 while 循环并等待 2 秒。然后我们将运行备份函数,当它第四次运行时将发生错误。如果将 c < 10 替换为 true,只要没有异常,它就会一直运行。这是一个简单的备份过程实现,它将在 X 时间后运行。

感谢阅读!

打招呼! Instagram | Twitter | LinkedIn | Medium

鏂囩珷鏉ユ簮锛�https://dev.to/lampewebdev/i-promise-this-is-a-practical-guide-to-async--await-39ek
PREV
🤓💻 我们可以从 Valorant 页面上的一个按钮中学到什么
NEXT
👓💻 如何在 Node.js 服务之间建立 Websocket 连接