翻译练习

原博客地址:JavaScript async/await: The Good Part, Pitfalls and How to Use

ES7中引进的async/await是对JavaScript的异步编程的一个极大改进。它提供了一种同步代码编写风格来获取异步资源的选项,却不阻塞主进程。然后,想很好地使用它也很棘手。在这篇文章中我们将通过不同的角度去探讨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关键词,代码看起来就像其他的的同步语言,比如说Python

最棒的地方不仅仅是可读性。async/await拥有浏览器的原生支持。目前为止,所有的主流浏览器都完全支持async函数。

原生支持意味着你不用转译代码。更重要的是,它便于调试。当你在函数的入口处设置一个断点,然后步入await这行代码,你将看到调试器在bookModel.fetchAll()执行的时候停了一会,然后移动到下一步.filter代码行。这比promise的情况简单多了,在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/awaitPromise进行比较,然后宣称async/awaitJavaScript异步编程进化的下一代,恕我不敢苟同。Async/await是一种改进而不仅仅是一种语法糖,它并不能完全改变我们的编程风格。

本质上,async函数仍然还是promise。在你正确的使用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) {
// 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);
}

简而言之,你需要异步地去思考工作量,然后使用await同步地编写代码。在负责的工作流中直接使用promise可能会更简单。

错误处理

使用promise时,一个异步的函数有两种可能返回的结果:resolved 值和rejected值。我们可以使用.then去处理正常情况,.catch去处理异常情况。然而,使用async/await时,异常处理可能比较难弄。

try…catch

最标准的(也是最推荐的)方式是使用try...catch语句。当await一个调用时,任何rejeced值都会被当做一个异常抛出。下面是一个例子:

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 }
}

被捕获到的错误正是被rejected的值。在我们捕获到异常以后,我们有以下几个处理方式:

  • 处理异常然后返回一个正常值。(在catch代码块中不返回任何值相当一返回undefined,这也算一个正常值。)
  • 如果你想让调用者处理它,那就把它抛出去。你也可以像throw error这样直接抛出整个error对象,这允许你在promise链中去使用async getBooksByAuthorWithAwait()函数(例如:你仍然可以像这样去调用getBooksByAuthorWithAwait().then(...).catch(error => ...));或者你可以使用Error对象对错误进行包装,像这样:throw new Error(error),这样,当错误在控制条打印出来的时候,这给你提供一个完整的错误堆栈跟踪。
  • Reject它,像return Promise.reject(error)这样。这根throw error一样,所以不推荐这样做。

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

  • 简单,传统。只要你有其他的语言的经验,如Java 或者 C++,没有任何困难去理解它。
  • 如果没有必要去处理每个步骤的错误的话,你可以在一个try...catch中去包裹多个await调用,这样可以在一个地方去处理错误。

这种做法也有一个缺陷。因为try...catch会捕获所有的异常,一些不经常被promise捕获的错误也会被捕获。想一下下面这个例子:

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"
}

运行这段代码,你在控制台会得到一个黑色字体的错误信息:ReferenceError: cb is not defined。这个错误是consol.log输出的,而不是JavaScript输出的。有时候这会是致命的:如果BookModel被一系列的函数调用深深包裹着,而其中的一个调用吞掉了这个错误,那么找到一个像这样的未定义错误将会十分困难。

使函数返回两个值

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

简而言之,你可以这样使用异步函数:

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

我们将要介绍的最后一种方法是继续使用catch

回想一下await的功能:它会等待promise完成它的工作。同时也回想一下promise.catch()也会返回一个promise。所以我们可以这样去写错误处理:

// 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关键词对JavaScript的异步编程来说绝对是一个进步。它使得代码的阅读和调试都更简单。然而,为了正确的使用它,你必须去完全的理解promise,因为它不在仅仅是语法糖,它的底层原理仍然是promise

希望这篇文章能让你对async/await有一些想法,也能让你避免一些常见的错误。

JavaScript async/await:优点、陷阱及如何使用的更多相关文章

  1. 【译】JavaScript async / await:好的部分,陷阱和如何使用

    async/await提供了一种使用同步样式代码异步访问资源的选项,而不会阻塞主线程.然而,使用它有点棘手.在本文中,我们将从不同的角度探讨async / await,并将展示如何正确有效地使用它们. ...

  2. JavaScript async/await 基础知识

    async 作用: async函数返回一个 Promise对象,无论内部有没有await关键字. await 作用: await等待的是一个表达式,这个表达式的计算结果是 Promise 对象 或者是 ...

  3. JavaScript - async/await 基础示例

    一个函数如果被 async 修饰,无论内部是否有 await的异步操作,都会返回一个 Promise 对象 demo 1 async function basicAsync() { let resul ...

  4. JavaScript 的 Async\/Await 完胜 Promise 的六

    参考:http://www.10tiao.com/html/558/201705/2650964601/1.html Node 现在从版本 7.6 开始就支持 async/await 了. 简介: A ...

  5. 七 vue学习 async/await

    1:  javaScript async/await: 调用async函数的时候,是异步的,函数后面的代码继续执行.! async / await是ES7的重要特性之一,也是目前社区里公认的优秀异步解 ...

  6. ES7 Async/Await 陷阱

    什么是Async/Await ES6新增了Promise函数用于简化项目代码流程.然而在使用promise时,我们仍然要使用callback,并且并不知道程序要干什么,例如: function doS ...

  7. JavaScript ES7 中使用 async/await 解决回调函数嵌套问题

    原文链接:http://aisk.me/using-async-await-to-avoid-callback-hell/ JavaScript 中最蛋疼的事情莫过于回调函数嵌套问题.以往在浏览器中, ...

  8. [转] Understanding JavaScript’s async await

    PS:Promise的用处是异步调用,这个对象使用的时候,call then函数,传一个处理函数进去,处理异步调用后的结果 Promise<Action>这样的对象呢,异步调用后的结果是一 ...

  9. JavaScript基础——深入学习async/await

    本文由云+社区发表 本篇文章,小编将和大家一起学习异步编程的未来--async/await,它会打破你对上篇文章Promise的认知,竟然异步代码还能这么写! 但是别太得意,你需要深入理解Promis ...

随机推荐

  1. ASP.Net Core 5.0 MVC 配置文件读取,Startup 类中ConfigureServices 方法、Configure 方法的使用

    配置文件读取 1. 新建FirstController控制器 在appsettings文件内容替换成以下代码 { "Position": { "Title": ...

  2. 【C#】对两张图片进行矩阵运算会怎么样?

    对两张图片进行矩阵运算会怎么样? 在学习<线性代数>的矩阵运算时,突然想到图片也可以算是一种矩阵,那么对图片进行矩阵的运算会出现什么样的效果呢?为了满足好奇,便用C#写了个对图片进行矩阵运 ...

  3. 【uva 11572】Unique Snowflakes(算法效率--滑动窗口,3种实现方法)

    题意:求长度为N的序列中,最长的一个无重复元素的连续子序列. 解法:[L,R]每次R++或L++延伸就可以得到答案. 实现:(1)next[],last[]--O(n): 1 #include< ...

  4. 【noi 2.6_8471】切割回文(DP)

    题意:给一个字符串,问至少切割几次使每子串都是回文的. 解法:f[i]表示前i个字符至少需要切割几次,预处理p[i][j]表示子串s[i]~s[j]是否为回文串.O(n^2) 另外,这题也类似&quo ...

  5. poj2926Requirements (曼哈顿距离)

    Description An undergraduate student, realizing that he needs to do research to improve his chances ...

  6. HDU 3336——Count the string

    It is well known that AekdyCoin is good at string problems as well as number theory problems. When g ...

  7. Codeforces Global Round 9 B. Neighbor Grid (构造,贪心)

    题意:给一个\(n\)X\(m\)的矩阵,矩阵中某个数字\(k\)表示其四周恰好有\(k\)个不为0的数字,你可以使任意位置上的数字变大,如果操作后满足条件,输出新矩阵,否则输出NO. 题解:贪心,既 ...

  8. HttpClient&&RestTemplate学习

    1. 什么是HttpClient HttpClient是Apache下面的子项目,可以提供高效的,最新的,功能丰富的支持HTTP协议的客户端编程工具包. 2. 为什么要学习HttpClient Htt ...

  9. SpringBoot 启动慢的解决办法

    项目集成了很多内容,有 700 多个类,IDEA 中启动一次需要 70 秒,非常影响开发效率. 研究问题原因发现有以下几种情况会导致启动速度慢,优化后启动只需 26 秒左右了: 1. 和网卡有关,禁用 ...

  10. HDU 4336 Card Collector(状压 + 概率DP 期望)题解

    题意:每包干脆面可能开出卡或者什么都没有,一共n种卡,每种卡每包爆率pi,问收齐n种卡的期望 思路:期望求解公式为:$E(x) = \sum_{i=1}^{k}pi * xi + (1 - \sum_ ...