探索 JavaScript 中的异步/等待函数

介绍

Promises为我们提供了一个更容易的方式来处理我们的代码中的不同步,以序列的方式. 考虑到我们的大脑不是设计用于有效地处理不同步,这是一个非常受欢迎的补充。

使用 async 函数实现的功能可以通过将承诺与 生成器相结合来重建,但 async 函数会为我们提供我们所需的东西,而无需额外的锅炉板代码。

简单的例子

在下面的示例中,我们首先声明一个函数,该函数在 2 秒后返回一个解决到``值的承诺,然后声明一个非同步函数,然后等待承诺解决,然后将消息登录到控制台:

 1function scaryClown() {
 2  return new Promise(resolve => {
 3    setTimeout(() => {
 4      resolve('🤡');
 5    }, 2000);
 6  });
 7}
 8
 9async function msg() {
10  const msg = await scaryClown();
11  console.log('Message:', msg);
12}
13
14msg(); // Message: 🤡 <-- after 2 seconds

<$>[注意] 'Wait' 是用来等待承诺解决或拒绝的新运算器,只能在同步函数内使用。

async函数的力量在涉及多个步骤时变得更加明显:

 1function who() {
 2  return new Promise(resolve => {
 3    setTimeout(() => {
 4      resolve('🤡');
 5    }, 200);
 6  });
 7}
 8
 9function what() {
10  return new Promise(resolve => {
11    setTimeout(() => {
12      resolve('lurks');
13    }, 300);
14  });
15}
16
17function where() {
18  return new Promise(resolve => {
19    setTimeout(() => {
20      resolve('in the shadows');
21    }, 500);
22  });
23}
24
25async function msg() {
26  const a = await who();
27  const b = await what();
28  const c = await where();
29
30  console.log(`${ a } ${ b } ${ c }`);
31}
32
33msg(); // 🤡 lurks in the shadows <-- after 1 second

但是,在上面的例子中,每个步骤是顺序完成的,每一个额外的步骤都在等待前的步骤解决或拒绝,然后继续。

1// ...
2
3async function msg() {
4  const [a, b, c] = await Promise.all([who(), what(), where()]);
5
6  console.log(`${ a } ${ b } ${ c }`);
7}
8
9msg(); // 🤡 lurks in the shadows <-- after 500ms

Promise.all 返回了解决值的数组,一旦所有传入承诺都解决了。

在上面,我们也使用一些美好的(https://andsky.com/tech/tutorials/js-object-array-destructuring-es2015)来简化我们的代码。

回归承诺

Async 函数总是返回一个承诺,因此以下可能不会产生您想要的结果:

1async function hello() {
2  return 'Hello Alligator!';
3}
4
5const b = hello();
6
7console.log(b); // [object Promise] { ... }

由于返回的是一个承诺,你可以做这样的事情:

1async function hello() {
2  return 'Hello Alligator!';
3}
4
5const b = hello();
6
7b.then(x => console.log(x)); // Hello Alligator!

或者只是这个:

1async function hello() {
2  return 'Hello Alligator!';
3}
4
5hello().then(x => console.log(x)); // Hello Alligator!

不同形式

到目前为止,在我们的示例中,我们将async函数视为函数声明,但我们还可以定义async函数表达式和async箭头函数:

Async 函数表达式

以下是我们第一个示例中的 async 函数,但定义为函数表达式:

1const msg = async function() {
2  const msg = await scaryClown();
3  console.log('Message:', msg);
4}

Async 箭头函数

以下是同一个例子,但这一次被定义为箭头函数:

1const msg = async () => {
2  const msg = await scaryClown();
3  console.log('Message:', msg);
4}

行为错误

关于async函数的另一个非常好的事情是,错误处理也完全同步地进行,使用良好的老 ** try...catch** 陈述。

 1function yayOrNay() {
 2  return new Promise((resolve, reject) => {
 3    const val = Math.round(Math.random() * 1); // 0 or 1, at random
 4
 5    val ? resolve('Lucky!!') : reject('Nope 😠');
 6  });
 7}
 8
 9async function msg() {
10  try {
11    const msg = await yayOrNay();
12    console.log(msg);
13  } catch(err) {
14    console.log(err);
15  }
16}
17
18msg(); // Lucky!!
19msg(); // Lucky!!
20msg(); // Lucky!!
21msg(); // Nope 😠
22msg(); // Lucky!!
23msg(); // Nope 😠
24msg(); // Nope 😠
25msg(); // Nope 😠
26msg(); // Nope 😠
27msg(); // Lucky!!

鉴于 async 函数总是返回承诺,您也可以像通常使用 catch 语句那样处理未处理的错误:

1async function msg() {
2  const msg = await yayOrNay();
3  console.log(msg);
4}
5
6msg().catch(x => console.log(x));

这种同步错误处理不仅在承诺被拒绝时起作用,而且在实际运行时间或语法错误发生时也起作用。在下面的示例中,第二次通过调用我们的 msg 函数,我们将传输一个数值,其原型链中没有 toUpperCase 方法。

 1function caserUpper(val) {
 2  return new Promise((resolve, reject) => {
 3    resolve(val.toUpperCase());
 4  });
 5}
 6
 7async function msg(x) {
 8  try {
 9    const msg = await caserUpper(x);
10    console.log(msg);
11  } catch(err) {
12    console.log('Ohh no:', err.message);
13  }
14}
15
16msg('Hello'); // HELLO
17msg(34); // Ohh no: val.toUpperCase is not a function

Async 函数与基于承诺的 APIS

正如我们在 Fetch API的先例中所展示的那样,基于承诺的Web API是对非同步函数的完美候选人:

 1async function fetchUsers(endpoint) {
 2  const res = await fetch(endpoint);
 3  let data = await res.json();
 4
 5  data = data.map(user => user.username);
 6
 7  console.log(data);
 8}
 9
10fetchUsers('https://jsonplaceholder.typicode.com/users');
11// ["Bret", "Antonette", "Samantha", "Karianne", "Kamren", "Leopoldo_Corkery", "Elwyn.Skiles", "Maxime_Nienow", "Delphine", "Moriah.Stanton"]

<$>[注] 浏览器支持: 截至2020年,全球94%的浏览器能够在 javascript中处理async/await。

结论

Async/Wait 函数之前, JavaScript 代码依赖于许多非同步事件(例如:对 API 进行大量调用的代码)_ 最终会导致一些人称之为回调地狱 - 一个功能和回调的链条,很难读取和理解。

Async 和 Wait 允许我们编写更清晰的非同步 JavaScript 代码。

Published At
Categories with 技术
Tagged with
comments powered by Disqus