async/await

有一种特殊的语法可以以更舒适的方式处理promises,称为“async / await”。理解和使用起来非常简单。

async-await是promise和generator的语法糖。只是为了让我们书写代码时更加流畅,当然也增强了代码的可读性。简单来说:async-await 是建立在 promise机制之上的,并不能取代其地位。

Async functions

先从关键字async说起,它被放在一个函数前面,像下面这样:

async function f(){    
    return ‘Hello world’;
}

函数前面的“async”一词意味着一件简单的事:函数总是返回一个promise。即使函数实际返回非promise值,在函数定义前加上“async”关键字也会指示JavaScript自动将该值包装在已解析的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确保函数返回一个promise,并在其中包含非promise。不仅如此。还有另一个关键字,await,它只能在async函数内部运行。

Await

语法:

// await只能放在async函数内部运行
let value = await promise;

await 后面可以跟任何的JS 表达式。虽然说 await 可以等很多类型的东西,但是它最主要的意图是用来等待 Promise 对象的状态被 resolved。如果await的是 promise对象会造成异步函数停止执行并且等待 promise 的解决,如果等的是正常的表达式则立即执行。

例如:

function sleep(second) {
    return new Promise((resolve, reject) => {
        setTimeout(() => { resolve(' enough sleep~'); }, second); }
    )
}

function normalFunc() {
    console.log('normalFunc');
}

async function awaitDemo() {
    await normalFunc();
    console.log('something, ~~');
    let result = await sleep(2000);
    console.log(result);// 两秒之后会被打印出来
}

awaitDemo();

注意:await字面意思是让JavaScript等到承诺结束,然后继续结果。这不会占用任何CPU资源,因为引擎可以同时执行其他任务:执行其他脚本,处理事件等。

这只是得到promise.then结果的更优雅的语法,易于读写。

不能await在常规功能中使用

如果我们尝试await在非异步函数中使用,则会出现语法错误:

function f () {
    let promise = Promise.resolve(1);
    let result = await promise; // Uncaught SyntaxError: await is only valid in async function
}

如果我们没有在函数之前写async,我们将得到此错误。如上所述,await只能在一个async function内部工作。

一个promise链式操作的例子,若用 ES5实现会有多层的回调,若用Promise 实现也需要多个then。一个是代码横向发展,另一个是纵向发展。

fetch('/article/promise-chaining/user.json')

  .then(response => response.json())

  .then(user => fetch(`https://api.github.com/users/${user.name}`))

  .then(response => response.json())

  .then(githubUser => new Promise(function (resolve, reject) {

    let img = document.createElement('img');

    img.src = githubUser.avatar_url;

    img.className = "promise-avatar-example";

    document.body.append(img);

    setTimeout(() => {

      img.remove();

      resolve(githubUser);

    }, 3000);

  }))

  // triggers after 3 seconds

  .then(githubUser => alert(`Finished showing ${githubUser.name}`));

用async/await重写它:

1.将.then()替换为await

2.让函数变成async

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 不能放在顶级作用域

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();

  ...

})();

await接受thenables

就像promise.then,await也允许使用thenable对象(那些具有可调用的then方法的对象)。同样,第三方对象可能不是一个promise,但是promise的兼容性表示,如果它支持.then方法,那么它就能用于await。

例如,这里await接受了new Thenable(1)

class Thenable {

  constructor(num) {
    this.num = num
  }

  then(resolve, reject) {
    alert(resolve) // function() {native code}

    // 1000ms后将this.num*2作为resolve值
    setTimeout(() => { resolve(this.num * 2), 1000 }) // (*)
  }
}

async function f() {
  // 等待1s,result变为2
  let result = await new Thenable(1)

  alert(result)
}

f()

如果await得到了一个带有then方法的非promise对象,它将会调用提供原生函数resolve、reject作为参数的方法,然后await一直等待,直到他们其中的一个被调用(在上面的例子它发生在(*)行)。

async方法

一个class方法同样能够使用async,只需要将async放在它之前就可以

就像这样:

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

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

含义相同:它确保返回的值是promise并启用await。

错误处理

如果一个promise正常resolve,那么await返回这个结果,但是在reject的情况下会抛出一个错误,就好像在那一行有一个throw语句一样。

async function f() {
  await Promise.reject(new Error('whoops!'))
}

和这个一样

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

在实际情况中,promise在reject抛出错误之前可能需要一段时间,所以await将会等待,然后才抛出一个错误。

我们可以使用try - catch语句捕获错误,就像在正常抛出中处理异常一样:

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,则异步函数调用生成的promise 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错误(能够在控制台里看到它),这时我们可以通过使用一个全局的事件处理器去捕获错误。

async/await 和 promise.then/catch 对比

当我们使用时async/await,我们很少需要.then,因为await为我们处理了等待。我们可以使用常规 try..catch 代替 .catch 。这通常(并非总是)更方便。

但是在代码的顶层,当我们在任何async函数之外时,我们在语法上无法使用await,这时候使用.then/catch来处理最终结果或捕获错误是正常的做法。

async / await 适用于 Promise.all

当我们需要等待多个 promise 时,我们可以将它们放进Promise.all然后await:

function sleep(second) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('request done! ' + new Date().getTime())
            resolve();
        }, second);
    })
}

async function bugDemo() {
    let p1 = sleep(1000);
    let p2 = sleep(1000);
    let p3 = sleep(1000);
    await Promise.all([p1, p2, p3]);
    console.log('over~');
}

bugDemo();

await in for循环(await必须在async函数的上下文中)

// 正常 for 循环

async function forDemo() {
    let arr = [1,2,3,4,5];
    for(leti = 0; i < arr.length; i ++) {
        await arr[i]; 
     }
}

forDemo();//正常输出

// 因为想要炫技把 for循环写成下面这样
async function forBugDemo() {
    let arr = [1,2,3,4,5]; 
    arr.forEach(item=>{
        await item;
    });
}

forBugDemo(); // Uncaught SyntaxError: await is only valid in async function

总结

放在一个函数前的async有两个作用:

1.使函数总是返回一个promise

2.允许在这其中使用await

promise前面的await关键字能够使JavaScript等待,直到promise处理结束。然后:

1.如果产生一个错误或者reject,异常就产生了,就像在那个地方调用了throw error一样。

2.否则,它会返回一个结果,我们可以将它分配给一个值

参考文章:https://javascript.info/async-await#async-functions

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容