"承诺"的终极解决方案

人生代码

共 6368字,需浏览 13分钟

 · 2021-02-20

有一种特殊的语法以一种更舒适的方式处理承诺,称为“async/await”。它非常容易理解和使用。

Async functions

让我们从async关键字开始。它可以放在函数前面,像这样:

async function f({
  return 1;
}

函数前面的“async”一词意味着一件简单的事情:函数总是返回promise。其他值自动包装在已解析的承诺中。

例如,该函数返回一个已解析的promise,其结果为1;让我们测试它:

async function f({
  return 1;
}

f().then(alert); // 1

我们可以显式地返回一个promise,它是一样的:

async function f({
  return Promise.resolve(1);
}

f().then(alert); // 1

因此,async确保该函数返回一个承诺,并将非承诺封装在其中。很简单,对吧?但不仅如此。还有另一个关键字await,它只在异步函数内部工作,非常酷。

Await

语法:

// works only inside async functions
let value = await promise;

关键字await使JavaScript等待,直到promise解决并返回结果。

下面是一个承诺在1秒内解决的例子:

async function f({

  let promise = new Promise((resolve, reject) => {
    setTimeout(() => resolve("done!"), 1000)
  });

  let result = await promise; // wait until the promise resolves (*)

  alert(result); // "done!"
}

f();

函数的执行“暂停”在(*)行,当promise结束时继续执行,result成为它的结果。所以上面的代码显示“done!”“一秒钟之内。

让我们强调一下:await实际上是暂停函数的执行,直到promise结束,然后使用promise结果继续执行。这不会消耗任何CPU资源,因为JavaScript引擎可以同时执行其他任务:执行其他脚本、处理事件等等。

这是一种比promise更优雅的获取promise结果的语法。然后,更容易读和写。

不能在常规函数中使用await

function f({
  let promise = Promise.resolve(1);
  let result = await promise; // Syntax error
}

让我们以承诺链接一章中的showAvatar()为例,并使用async/await重写它:

我们需要替换。然后调用await。

我们也应该使函数异步,以使它们工作。

async function showAvatar({

  // read our JSON
  let response = await fetch('/article/promise-chaining/user.json');
  let user = await response.json();

  // read github user
  let githubResponse = await fetch(`https://api.github.com/users/${user.name}`);
  let githubUser = await githubResponse.json();

  // show the avatar
  let img = document.createElement('img');
  img.src = githubUser.avatar_url;
  img.className = "promise-avatar-example";
  document.body.append(img);

  // wait 3 seconds
  await new Promise((resolve, reject) => setTimeout(resolve, 3000));

  img.remove();

  return githubUser;
}

showAvatar();

await在顶级代码中不起作用

刚刚开始使用await的人往往会忘记这样一个事实:我们不能在顶级代码中使用await。例如,这将不起作用:

// syntax error in top-level code
let response = await fetch('/article/promise-chaining/user.json');
let user = await response.json();

但是我们可以把它包装成一个匿名异步函数,像这样:

(async () => {
  let response = await fetch('/article/promise-chaining/user.json');
  let user = await response.json();
})();

等待接受“thenables”

像承诺。然后,await允许我们使用thenable对象(带有可调用then方法的对象)。其理念是,第三方对象可能不是承诺,但与承诺兼容:如果它支持.那么,与await一起使用它就足够了。

这是一个演示的Thenable类;下面的await接受它的实例:

class Thenable {
  constructor(num) {
    this.num = num;
  }
  then(resolve, reject) {
    alert(resolve);
    // resolve with this.num*2 after 1000ms
    setTimeout(() => resolve(this.num * 2), 1000); // (*)
  }
}

async function f({
  // waits for 1 second, then result becomes 2
  let result = await new Thenable(1);
  alert(result);
}

f();

如果await获得一个带有.then的非Promise对象,它将调用该方法,并提供内置函数resolve和reject作为参数(就像它对常规Promise executor所做的那样)。然后await等待,直到其中一个被调用(在上面的例子中,它发生在行(*)中),然后继续处理结果。

异步类方法

要声明一个异步类方法,只需在它的前面加上async:

class Waiter {
  async wait() {
    return await Promise.resolve(1);
  }
}

new Waiter()
  .wait()
  .then(alert); // 1

错误处理

如果promise正常解析,则await promise返回结果。但是在拒绝的情况下,它抛出错误,就像在那一行有一个throw语句一样。

这段代码:

async function f({
  await Promise.reject(new Error("Whoops!"));
}

和这个是一样的:

async function f({
  throw new Error("Whoops!");
}

在实际情况下,承诺可能需要一段时间才会被拒绝。在这种情况下,在await抛出错误之前会有延迟。

我们可以使用try..catch,和普通的throw一样:

async function f({

  try {
    let response = await fetch('http://no-such-url');
  } catch(err) {
    alert(err); // TypeError: failed to fetch
  }
}

f();

在出现错误的情况下,控件跳转到catch块。我们也可以换行:

async function f({

  try {
    let response = await fetch('/no-user-here');
    let user = await response.json();
  } catch(err) {
    // catches errors both in fetch and response.json
    alert(err);
  }
}

f();

如果我们不 try…catch,然后异步函数f()调用生成的承诺将被拒绝。我们可以添加.catch来处理它:

async function f({
  let response = await fetch('http://no-such-url');
}

// f() becomes a rejected promise
f().catch(alert); // TypeError: failed to fetch // (*)

如果我们忘记在那里添加.catch,那么我们会得到一个未处理的promise错误(在控制台中可见)。我们可以使用全局unhandledrejection事件处理程序来捕获这样的错误,如“带承诺的错误处理”一章所述。

async/await and promise.then/catch

当我们使用async/await时,我们很少需要.then,因为await处理等待我们的操作。我们可以经常try…catch代替.catch。这通常(但不总是)更方便。

但是在代码的顶层,当我们在任何异步函数之外时,我们在语法上不能使用await,所以通常的做法是添加.then/catch来处理最终结果或失败错误,就像上面例子中的(*)行。

async/await works well with Promise.all

当我们需要等待多个承诺时,我们可以用承诺来包装它们。一切都在等待:

// wait for the array of results
let results = await Promise.all([
  fetch(url1),
  fetch(url2),
  ...
]);


浏览 28
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报