<li id="2aw4k"></li>
  • <div id="2aw4k"><tr id="2aw4k"></tr></div>
  • <div id="2aw4k"><tr id="2aw4k"></tr></div>
    <center id="2aw4k"><small id="2aw4k"></small></center><center id="2aw4k"><small id="2aw4k"></small></center>
    首页»JavaScript»如何正确使用async/await?

    如何正确使用async/await?

    来源:infoq 发布时间:2018-08-01 阅读次数:

      ES7引入的async/await是JavaScript异步编程的一个重大改进,提供了在不阻塞主线程的情况下使用同步代码异步访?#39318;?#28304;的能力。在本文中,我们将从不同的角度探索async/await,并演示如何正确有效地使用它们。

     async/await的好处

      async/await给我们带来的最重要的好处是同步编程风格。我们来看一个例子。

    // async/await
    async getBooksByAuthorWithAwait(authorId) {
      const books = await bookModel.fetchAll();
      return books.filter(b => b.authorId === authorId);
    }
    // promise
    getBooksByAuthorWithPromise(authorId) {
      return bookModel.fetchAll()
        .then(books => books.filter(b => b.authorId === authorId));
    }

      很显然,async/await比promise更容易理解。如果忽略掉await关键字,代码看起来与其他?#25105;?#19968;门同步语言一样(如Python)。

      除了可读性,async/await还对浏览器提供了原生支持。目前所有的主流浏览器都完全支持异步功能。

    ??

      原生支持意味着不需要编译代码。更重要的是,它调?#20113;?#26469;很方便。在函数入口设置断点并执行跳过await行之后,调试器会在bookModel.fetchAll()执行时暂停一会儿,然后移动到下一行(也就是.filter)!这比使用promise要容易调试得多,因为你必须在.filter这一行设置另一个断点。

    ??

      另一个好处是async关键字,尽管看起来不是很明显。它声明getBooksByAuthorWithAwait()函数的返回值是一个promise,因此调用者可以安全地调用getBooksByAuthorWithAwait().then(…)或await getBooksByAuthorWithAwait()。比如像下面这段代码:

    getBooksByAuthorWithPromise(authorId) {
      if (!authorId) {
        return null;
      }
      return bookModel.fetchAll()
        .then(books => books.filter(b => b.authorId === authorId));
      }
    }

      在上面的代码中,getBooksByAuthorWithPromise可能返回一个promise(正常情况)或null(异常情况),在这种情况下,调用者无法安全地调用.then()。而如果使用async声明,则不会出现这种情况。

     async/await可能会引起误解

      有些文章将async/await与promise进行了比较,并声称它是JavaScript异步编程演变的下一代,但我非常不同意这?#36824;?#28857;。async/await是一?#25351;?#36827;,但它不过是一种语法糖,它不会完全改变我们的编程风格。

      从本质上讲,异步函数仍然是promise。在正确使用异步函数之前,你必须了解promise,更糟糕的是,大部分时间需要同时使用promise和异步函数。

      考虑上例中的getBooksByAuthorWithAwait()和getBooksByAuthorWithPromises()函数。请注意,它们不仅功能相同,接口也是完全一样?#27169;?/p>

      这意味着如果直接调用getBooksByAuthorWithAwait(),它将返回一个promise。

      不过这不一定是件坏事。只是await会给人一?#25351;?#35273;:“它可以将异步函数转换为同步函数”。但这实际上是错误的。

     async/await的陷阱

      那么人们在使用async/await时可能会犯什?#21019;?#35823;?下面列举了一些常见的错误。

      太过串行化

      虽然await可以让你的代码看起来像是同步?#27169;?#20294;请记住,它们仍然是异步?#27169;?#35201;避免太过串行化。

    async getBooksAndAuthor(authorId) {
      const books = await bookModel.fetchAll();
      const author = await authorModel.fetch(authorId);
      return {
        author,
        books: books.filter(book => book.authorId === authorId),
      };
    }

      上面的代码在逻辑上看起来很正确,但这样做其实是不对的。

    1. await bookModel.fetchAll()将等到fetchAll()返回。
    2. 然后await authorModel.fetch(authorId)将被调用。

      注意,authorModel.fetch(authorId)不?#35272;礲ookModel.fetchAll()的结果,事实上它们可以并行调用!然而,因为在这里使用了await,两个调用变成串行?#27169;?#24635;的执行时间将比并行版本要长得多。

      正确的方法应该是:

    async getBooksAndAuthor(authorId) {
      const bookPromise = bookModel.fetchAll();
      const authorPromise = authorModel.fetch(authorId);
      const book = await bookPromise;
      const author = await authorPromise;
      return {
        author,
        books: books.filter(book => book.authorId === authorId),
      };
    }

      或者更糟糕的是,如果你想要逐个获取物?#38750;?#21333;,你必须使用promise:

    async getAuthors(authorIds) {
      // WRONG, this will cause sequential calls
      // const authors = _.map(
      //   authorIds,
      //   id => await authorModel.fetch(id));
    // CORRECT
      const promises = _.map(authorIds, id => authorModel.fetch(id));
      const authors = await Promise.all(promises);
    }

      总之,你仍然需要将流程视为异步?#27169;?#28982;后使用await写出同步的代码。在复杂的流程中,直接使用promise可能更方便。

     错误处理

      在使用promise时,异步函数有两个可能的返回值。对于正常情况,可以使用.then(),而对于异常情况,则使用.catch()。不过在使用async/await时,错误处理可能会变得有点蹊跷。

      try…catch

      最标准?#27169;?#20063;是我推荐?#27169;?#26041;法是使用try…catch语句。在调用await函数时,如果出现非正常状况就会跑出异常。比如:

    class BookModel {
      fetchAll() {
        return new Promise((resolve, reject) => {
          window.setTimeout(() => { reject({'error': 400}) }, 1000);
        });
      }
    }
    // async/await
    async getBooksByAuthorWithAwait(authorId) {
    try {
      const books = await bookModel.fetchAll();
    } catch (error) {
      console.log(error);    // { "error": 400 }
    }

      在捕捉到异常之后,我们有几种方法?#21019;?#29702;它:

    • 处理异常,并返回一个正常值。(不在catch块中使用任何return语句相当于使用return undefined,undefined也是一个正常值。)
    • 如果你想让调用者?#21019;?#29702;它,就将它抛出。你可以直接抛出错误对象,比如throw error,这样就可以在promise链中使用await getBooksByAuthorWithAwait()函数(也就是像getBooksByAuthorWithAwait().then(...).catch(error => …)这样调用它)。或者你可以将错误包装成Error对象,比如throw new Error(error),那么在控制台中显示这个错误时它将给出完整的堆栈跟踪信息。
    • 拒绝它,比如return Promise.reject(error)。这相当于throw error,因此不推荐使用。

      使用try…catch的好处是:

    • 简单,传?#22330;?#21482;要你有其他语言(如Java或C++)的编程经验,要理解这一点就不会有任何困难。
    • 如果没有必要逐步进?#20889;?#35823;处理,那么可以在单个try…catch块中包装多个await调用,这样就可以在一个地方处理所?#20889;?#35823;。

      这种方法也有一个缺陷。由于try...catch会捕获代码块中的每个异常,所以通常不会被promise捕获的异常?#19981;?#34987;捕获到。比如:

    class BookModel {
      fetchAll() {
        cb();    // note `cb` is undefined and will result an exception
        return fetch('/books');
      }
    }
    try {
      bookModel.fetchAll();
    } catch(error) {
      console.log(error);  // This will print "cb is not defined"
    }

      运?#20889;?#20195;码,你将会在控制台看到“ReferenceError:cb is not defined”错误,消息的颜色是黑色的。错误消息是通过console.log()输出?#27169;?#32780;不是JavaScript本身。有时候这可能是致命?#27169;?#22914;果BookModel被包含在一系列函数调用中,并?#31227;?#20013;一个调用把错误吞噬掉了,那么?#19994;?#36825;样的undefined错误将非常困难。

     让函数返回两个值

      错误处理的另一种方式是受到了Go语言启发,它允许异步函数返回错误和结果。

      简单地说,我们可以像这样使用异步函数:

    [err, user] = await to(UserModel.findById(1));

      我个人不?#19981;?#36825;种方法,因为它将Go语言的风格带入到了JavaScript中,感觉不自然。但在某些情况下,这可能相当有用。

      使用.catch

      我们要介绍的最后一种方法是继续使用.catch()。

      回想一下await的功能?#26680;?#23558;等待promise完成工作。另外,promise.catch()?#19981;?#36820;回一个promise!所以我们可以这样进?#20889;?#35823;处理:

    // books === undefined if error happens,
    // since nothing returned in the catch statement
    let books = await bookModel.fetchAll()
      .catch((error) => { console.log(error); });

      这种方法有两个小问题:

    • 它是promise和异步函数的混合体。你仍然需要了解promise的工作原理才能看懂这段代码。
    • 错误处理出现在普通代码逻辑之前,这样不直观。

     结论

      ES7引入的async/await关键字绝对?#23884;訨avaScript异步编程的重大改进。它让代码更易于阅读和调试。然而,要正确使用它们,人们必须了解promise。它们不过是语法糖,本质?#20808;?#28982;是promise。

      英文原?#27169;?a href="https://hackernoon.com/javascript-async-await-the-good-part-pitfalls-and-how-to-use-9b759ca21cda">https://hackernoon.com/javascript-async-await-the-good-part-pitfalls-and-how-to-use-9b759ca21cda

    QQ群:WEB开发者官方群(515171538),验证消息:10000
    微信群:?#26377;?#32534;微信 849023636 邀请您加入,验证消息:10000
    提示:更多精彩内容关注微信公众号:全栈开发者中?#27169;╢sder-com)
    es7
    网友评论(共0条评论) 正在载入评论......
    理智评论文明上网,拒绝恶意谩骂 发表评论 / 共0条评论
    登录会员中心
    大乐透彩票预测
    <li id="2aw4k"></li>
  • <div id="2aw4k"><tr id="2aw4k"></tr></div>
  • <div id="2aw4k"><tr id="2aw4k"></tr></div>
    <center id="2aw4k"><small id="2aw4k"></small></center><center id="2aw4k"><small id="2aw4k"></small></center>
    <li id="2aw4k"></li>
  • <div id="2aw4k"><tr id="2aw4k"></tr></div>
  • <div id="2aw4k"><tr id="2aw4k"></tr></div>
    <center id="2aw4k"><small id="2aw4k"></small></center><center id="2aw4k"><small id="2aw4k"></small></center>