美文网首页
async/await

async/await

作者: Xludan | 来源:发表于2019-05-08 18:59 被阅读0次

    有一种特殊的语法可以以更舒适的方式处理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

    相关文章

      网友评论

          本文标题:async/await

          本文链接:https://www.haomeiwen.com/subject/cubvoqtx.html