zoukankan      html  css  js  c++  java
  • 【译】async/await 优点、陷阱以及如何使用 (经验总结)

    https://juejin.im/post/5b2075375188257d4044c783

    ES7 推出的 async/await 特性对 JS 的异步编程是一个重大的改进。在不阻塞主线程的情况下,它为我们提供了使用同步代码风格去异步获取资源的能力。当然使用它也是需要一些技巧,这篇文章我们从不同角度去探索 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 关键字,那么代码就如同其他同步编程语言,如 Python

    优点不仅仅是可读性,async/await 已经被浏览器原生支持。如今,所有主流浏览器已经完全支持

    原生支持,意味着你不必转换代码,而更重要的是有利于调试。当你在函数的 await 代码行打上断点,然后步进到下一行时,你会发现调试器在 bookModel.fetchAll() 操作的时候进行了短暂的停留,然后才真正的步进到 .filter 代码行!这比 promise 调试更方便,因为你需要在 .fliter 代码行再打一个断点。

    另一个很少被人注意到的优点是 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/awaitpromise 并声称它是下一代 JS 异步编程,而我不同意这种观点。async/await 的确是一种改进,但它不过是个语法糖,不会彻底改变我们的编程风格。

    本质来说,async 函数仍然是 promises。在正确的使用 async 之前,你需要理解 promise,可能你在使用 async 的过程中也需要使用到 promise

    回顾一下上面代码中的 getBooksByAuthorWithAwait()getBooksByAuthorWithPromises() 函数,他们不仅功能完全相同,而且具有相同的接口。

    这意味着,直接调用 getBooksByAuthorWithAwait() 会返回一个 promise

    这不见得是件坏事,而多数人认为 await 可以让异步函数变为同步函数的想法才是错误的。

    async/await陷阱

    哪么我们在使用 async/await 会犯哪些错误呢?以下是一些常见点。

    太同步化

    尽管 await 能让我们的代码看起来同步化,但要牢记它们仍然是异步的内容,所以值得我们去关注代码以避免太同步化。

    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) 并不依赖 bookModel.fetchAll() 的结果,实际上他们可以并行执行! 而在这里使用 await 会导致两个函数串行执行,而执行时间也会比并行执行长。

    这是正确的做法:

    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),
      };
    }
    复制代码

    而如果你想依次获取一个列表中的所有项,你必须依赖 promises

    async getAuthors(authorIds) {
      // 错误,这会导致`串行执行`
      // const authors = _.map(
      //   authorIds,
      //   id => await authorModel.fetch(id));
    
      // 正确
      const promises = _.map(authorIds, id => authorModel.fetch(id));
      const authors = await Promise.all(promises);
    }
    复制代码

    简而言之,你仍然需要把工作流当成是异步的,然后尝试使用 await 去写同步代码。在更加复杂的工作流中,直接使用 promise 可能更方便。

    错误处理

    结合 promises,一个异步函数只有两个可能的返回值:resolve值reject值,然后我们可以使用 .then() 处理正常情况、.catch() 处理异常情况。但是 async/await 的错误处理就需要点技巧了。

    try...catch

    最常见(也是我推荐)的方法就是使用 try..catch。当 await 一个操作时,操作中任何 reject值 都会当作异常抛出。见代码:

    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 }
    }
    复制代码

    输出的错误对象正是 reject值。捕获异常之后,我们可以使用如下方法处理它们:

    • 处理异常,返回一个正常值(在 catch 代码块不使用 return 语句等同于 return undefined;,当然这也算是个正常值)。
    • 如果你想让调用者处理异常,那就抛出。你可以直接抛出异常对象,如 throw error,这样允许你在 async getBooksByAuthorWithAwait() 函数上使用 promise 链式操作(即:getBooksByAuthorWithAwait().then(...).catch(error => ...));或者使用 Error 对象包装你的错误对象,如 throw new Error(error),这样在控制台查看错误时,你可以看到完整的堆栈记录。
    • reject错误对象,如 return Promise.reject(error)。这等同于第一种做法,所以不推荐。

    使用 try...catch 的好处如下:

    • 简单、传统,如果你有诸如 JavaC++ 编程语言经历,理解起来不费事。
    • 在一个 try...catch 代码块中你可以在 try 代码块包裹多行 await 语句,并且如果前置错误处理没有必要的话,你可以在一个地方(即 catch 代码块)处理错误。

    这个方案仍然有它的瑕疵,try...catch 可以捕获代码块内的所有错误,包括那些不被 promises 捕获的错误。见代码:

    class BookModel {
      fetchAll() {
        cb();    // `cb` 因为没有被定义所有会导致异常
        return fetch('/books');
      }
    }
    try {
      bookModel.fetchAll();
    } catch(error) {
      console.log(error);  // 这里打印 "cb is not defined"
    }
    复制代码

    运行这段代码,你会在控制台得到 ReferenceError: cb is not defined 黑色字体输出信息。你要知道,这里的错误是通过 console.log() 输出的,并不是 JS 本身抛出(JS 抛出错误是红色字体)。有时这会很致命:如果 BookModel 被其它一些函数调用深深嵌套、包裹,其中一个调用吞并异常,那么想找到例子中的这种错误就会变得极其困难。

    让函数返回所有值

    Go 语言启发,另一种处理错误的方法就是允许 async 函数返回异常结果两个值(请参阅 How to write async await without try-catch blocks in Javascript),即你可以这样使用 async 函数:

    [err, user] = await to(UserModel.findById(1));
    复制代码

    我个人不建议使用这种实现,因为它把 Go 语言的风格带到了 JS,这让我感觉很不自然,但是个别情况下,使用它是极其合适的。

    使用.catch()

    最后一个方法就是继续使用 .catch()

    回想一下 await 的作用:它等待 promise 完成工作,也请记住 promise.catch() 也会返回一个 promise!所以我们可以这些处理错误:

    // 如果发生异常,但是 catch 语句没有显示返回,那么 books === undefined
    let books = await bookModel.fetchAll()
      .catch((error) => { console.log(error); });
    复制代码

    这个实现有两个瑕疵:

    • 它是 promiseasync 的混合函数。你需要理解 promise 才能读懂它。
    • 错误处理在返回之前,这不是很直观。

    结论

    ES7async/await 特性对 JS 异步编程是个巨大的改进。它让代码可读性更好、更方便调试。但是想要正确的使用他们,你必须彻底了解 promise。因为它只是个语法糖,它依赖的技术仍然是 promise

  • 相关阅读:
    scikit_learn 官方文档翻译(集成学习)
    机器学习之SVM与逻辑回归的联系和区别
    有序数组寻找中位数以及寻找K大元素
    有向图算法之拓扑排序
    机器学习之离散型特征处理--独热码(one_hot_encoding)
    计算广告学(2)--广告有效性模型
    机器学习实战--k-均值聚类
    SonarQube 扫描代码,SonarQube 进行代码质量检查
    Docker 搭建 Nexus3
    informix 安装 linux 客户端
  • 原文地址:https://www.cnblogs.com/porter/p/13343524.html
Copyright © 2011-2022 走看看