JavaScript async/await:优点、陷阱及如何使用
翻译练习
原博客地址: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/await
和Promise
进行比较,然后宣称async/await
是JavaScript
异步编程进化的下一代,恕我不敢苟同。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),
};
}
这段代码看起来逻辑上是正确的,但事实上它是错误的。
await bookModel.fetchAll()
会一直等到fetchAll()
返回。- 然后
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:优点、陷阱及如何使用的更多相关文章
- 【译】JavaScript async / await:好的部分,陷阱和如何使用
async/await提供了一种使用同步样式代码异步访问资源的选项,而不会阻塞主线程.然而,使用它有点棘手.在本文中,我们将从不同的角度探讨async / await,并将展示如何正确有效地使用它们. ...
- JavaScript async/await 基础知识
async 作用: async函数返回一个 Promise对象,无论内部有没有await关键字. await 作用: await等待的是一个表达式,这个表达式的计算结果是 Promise 对象 或者是 ...
- JavaScript - async/await 基础示例
一个函数如果被 async 修饰,无论内部是否有 await的异步操作,都会返回一个 Promise 对象 demo 1 async function basicAsync() { let resul ...
- JavaScript 的 Async\/Await 完胜 Promise 的六
参考:http://www.10tiao.com/html/558/201705/2650964601/1.html Node 现在从版本 7.6 开始就支持 async/await 了. 简介: A ...
- 七 vue学习 async/await
1: javaScript async/await: 调用async函数的时候,是异步的,函数后面的代码继续执行.! async / await是ES7的重要特性之一,也是目前社区里公认的优秀异步解 ...
- ES7 Async/Await 陷阱
什么是Async/Await ES6新增了Promise函数用于简化项目代码流程.然而在使用promise时,我们仍然要使用callback,并且并不知道程序要干什么,例如: function doS ...
- JavaScript ES7 中使用 async/await 解决回调函数嵌套问题
原文链接:http://aisk.me/using-async-await-to-avoid-callback-hell/ JavaScript 中最蛋疼的事情莫过于回调函数嵌套问题.以往在浏览器中, ...
- [转] Understanding JavaScript’s async await
PS:Promise的用处是异步调用,这个对象使用的时候,call then函数,传一个处理函数进去,处理异步调用后的结果 Promise<Action>这样的对象呢,异步调用后的结果是一 ...
- JavaScript基础——深入学习async/await
本文由云+社区发表 本篇文章,小编将和大家一起学习异步编程的未来--async/await,它会打破你对上篇文章Promise的认知,竟然异步代码还能这么写! 但是别太得意,你需要深入理解Promis ...
随机推荐
- Codeforces Round #635 (Div. 2)
Contest Info Practice Link Solved A B C D E F 4/6 O O Ø Ø O 在比赛中通过 Ø 赛后通过 ! 尝试了但是失败了 - 没有尝试 Sol ...
- Codeforces Round #671 (Div. 2)
比赛链接:https://codeforces.com/contest/1419 A. Digit Game 题意 给出一个 $n$ 位数,游戏规则如下: 1-indexed Raze标记奇数位 Br ...
- BZOJ1150 [CTSC2007]数据备份Backup 链表+小根堆
BZOJ1150 [CTSC2007]数据备份Backup 题意: 给定一个长度为\(n\)的数组,要求选\(k\)个数且两两不相邻,问最小值是多少 题解: 做一个小根堆,把所有值放进去,当选择一个值 ...
- 【poj 1061】青蛙的约会(数论--拓展欧几里德 求解同余方程)
题意:已知2只青蛙的起始位置 a,b 和跳跃一次的距离 m,n,现在它们沿着一条长度为 l 的纬线(圈)向相同方向跳跃.问它们何时能相遇?(好有聊的青蛙 (΄◞ิ౪◟ิ‵) *)永不相遇就输出&quo ...
- 【uva 11082】Matrix Decompressing(图论--网络流最大流 Dinic+拆点二分图匹配)
题意:有一个N行M列的正整数矩阵,输入N个前1~N行所有元素之和,以及M个前1~M列所有元素之和.要求找一个满足这些条件,并且矩阵中的元素都是1~20之间的正整数的矩阵.输入保证有解,而且1≤N,M≤ ...
- 【noi 2.2_7891】一元三次方程求解(二分枚举+输出程序运行时间)
对于noi上的题有2种解法: 1.数据很小(N=100),可以直接打for循环枚举和判断. 2.不会"三分",便用二分.利用"两根相差>=1"和 f(x1 ...
- Linux系统编程【3.1】——编写ls命令
ls命令简介 老规矩,直接在终端输入:man ls (有关于man命令的简介可以参考笔者前期博客:Linux系统编程[1]--编写more命令) 可以看到,ls命令的作用是显示目录中的文件名,它带有可 ...
- Nginx location相关配置说明
Nginx location相关配置说明 基于不同的IP.不同的端口以及不用得域名实现不同的虚拟主机,依赖于核心模块ngx_http_core_module实现. 新建PC web站点 [ ...
- Pangolin 安装测试 Installation & Examination (Ubuntu 20.04)
Pangolin 安装测试 Installation & Examination (Ubuntu 20.04) 如题所述,这是一个比较轻松的 Pangolin 安装配置方法,同样是基于 WSL ...
- sqli-libs(4) 双引号报错
经测试,发现单引号不报错,而双引号却报错了 通过查看源码,发现下图中红色的箭头,如果不知道是什么意思,我们可以复制出来看看是什么含义: <?php$id=1;$id='"' .$id. ...