8

“async/await”是一种与“promise”协作的特殊的语法。它使得异步工作更加容易理解和使用。

Async函数

我们从async关键词开始,它可以被放置在任何函数的开头位置,比如:

async function f() {
  return 1;
}

这里的async表示:该函数将始终返回一个promise。即使您的代码没有显式返回一个promise,在JavaScript运行时也会自动包装一个promise,用于返回指定的值。

在这个例子中,这段代码将会返回一个result为1的promise

async function f() {
  return 1;
}

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

当然,我们也可以显式的返回一个promise:

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

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

async确保了函数会返回一个promise。挺简单的对吧?接下来,是另一个关键词await,仅仅能在async标记的函数中生效。

Await

语法说明:

//该段代码仅仅能在 async 标记的函数中生效
let value = await promise;

关键词await确保JavaScript运行时将会等待promise执行完毕并返回结果。
下面是一段使用promise并在一秒后返回结果的例子:

async function f() {

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

  let result = await promise; // 等待至promise获得结果 (*)

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

f();

该函数在运行至await时,执行了“pauses”操作,直至promise执行完毕后重新执行接下来的代码。所以该段代码将在一秒后显示“done!”。
让我们强调一遍:await将顺序使得JavaScript的运行时等待promise执行完毕,而后继续运行余下代码。等待时的操作并不会消耗任何CPU资源,因为此时的运行时可以同时执行其他操作:执行其他的代码,处理事件逻辑等。
这仅仅是一项相对promise.than更为优雅的语法来获取promise的运行结果,更容易阅读和编写代码而已。


不能将await用于任何标准函数

如果您尝试将await运行在任何未标记为async的函数中,都会产生一个语法错误

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

我们如果没有使用async标记函数,那么我们就会得到这个语法错误。换句话说,await仅可以运行在async function中。


让我们修改 Promises chaining 中的例子,使用async/await来重写这个例子。

  1. 我们需要将.then替换为await
  2. 我们需要将函数修改为async function
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时,总是容易忘记必须在async function内部使用。比如以下代码将会报错:

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

所以我们需要声明一个async function来包裹该段代码。



await 可以接受 thenables

类似promise.thenawait准许使用then方法。需要申明的是,这里指的是一个非promise对象,但它持有.then方法,那么就可以配合await使用。
比如以下例子,await接受new Thenable(1):

class Thenable {
  constructor(num) {
    this.num = num;
  }
  then(resolve, reject) {
    alert(resolve); // function() { native code }
    // 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对象组合,同时其支持resolvereject两个方法作为参数。那么await将会等待其中之一的函数被调用(注释(*)所在行)并在之后继续运行剩余代码。



Async方法

类函数亦可以被定义为异步函数,仅需将async置于函数声明前。
类似于:

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

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

这和之前的其他代码端是一样的作用:应用await返回一个promise对象。


错误处理

如果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()时触发rejected。我们也可以添加.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 chaining


async/await 和 promise.then/catch

当我们使用async/await时,我们仅仅需要.then,因为await会接受正确结果。我们可以使用try…catch来取代.catch。同时这也将更为便利。
但处于顶层代码逻辑时,我们的逻辑代码处在async function以外,我们并不能直接使用await,所以添加.then/catch代码块来获取最终结果是更为普遍的做法。



async/await与Promise.all相互合作

当我们需要等待多个promises时,我们可以使用Promise.all之后使用await:

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

假如触发了一个错误,它也会和其他promise一样工作:从运行失败的Promise.all节点中断,之后我们可以通过try…catch来捕获错误。


总结

async定以后的函数有两层作用:

  1. 确保它总是返回一个promise。
  2. 允许在函数内部使用await

await关键词确保js运行时将会等待promise处理完毕,并且:

  1. 如果触发了一个运行错误,promise运行中断,并在改处类似触发throw error
  2. 此外,它将返回一个结果,所以我们可以将其赋值给一个变量。

我们一起提供了一个伟大的框架来更为简便的完成异步操作。

有了async/await的帮组,我们可以大幅减少使用promise.then/catch,但我们依然不应该忘记这些技术是基于promises,很可能我们会不得不继续使用promise的方法。同时,Promise.all相当适合等待多个任务顺序执行的操作。


不会爬树的猴
68 声望0 粉丝