译者按: 通过真实的代码示例感受Async/Await的力量。
为了保证可读性,本文采用意译而非直译。另外,本文版权归原作者所有,翻译仅用于学习。
既然Node.js 8已经LTS了,我想大家是时候试一试Async/Await特性了,真的很好用!它可以帮助我们用同步的方式写异步代码,极大地提高了代码的可读性。在过去的2年时间里,Promise给我们带来了不少便利,同时也让我们有一些失望。
这边博客,我将介绍一个真实的代码示例,它是一个REST API的controller。通过展示我们如何从Promise切换到async/await,你讲能够体会到Async/Await的神奇之处!
Promise示例
下面是我的工作项目中真实的Controller代码:
const BPromise = require('bluebird');
const { WrongCredentialsError, DBConnectionError, EmailError } = require('./../errors');
/** * Emulate an Express.js route call as an example */ loginController({}, { json: response => console.log(response) }, null)
function loginController (req, res, err) { const { email, password } = req;
let user;
BPromise.try(() => validateUserInput(req)) .then(() => fetchUserByEmail(email)) .then(fetchedUser => user = fetchedUser) .then(() => comparePasswords(req.password, user.password)) .then(() => markLoggedInTimestamp(user.userId)) .then(() => sendEmail(user.userId)) .then(() => generateJWT(user)) .then(token => res.json({ success: true, token })) .catch(WrongCredentialsError, () => res.json({ success: false, error: 'Invalid email and/or password' })) .catch(EmailError, DBConnectionError, () => res.json({ success: false, error: 'Unexpected error, please try again' })) .catch(() => res.json({ success: false })) }
/** * Validate input from Request * * @param {Object} input * @throws {WrongCredentialsError} * @returns {Void} */ function validateUserInput(input) { if (!input.email || !input.password) { throw new WrongCredentialsError(); } }
/** * Fetch a User from the DB by Email * * @throws WrongCredentialsError * @throws DBConnectionError * @returns {BPromise} */ function fetchUserByEmail(email) { const user = { userId: 'DUMMY_ID', email: 'konmpar@gmail.com', password: 'DUMMY_PASSWORD_HASH' } return new BPromise(resolve => resolve(user)); }
/** * Compare two password * * @param {String} inputPwd * @param {String} storedPwd * @throws {WrongCredentialsError} * @returns {Void} */ function comparePasswords(inputPwd, storedPwd) { if (hashPassword(inputPwd) !== storedPwd) { throw new WrongCredentialsError(); } }
/** * Hash password * * @param {String} password * @returns {String} */ function hashPassword(password) { return password; }
/** * Mark a user's logged in timestamp * * @param {String} userId * @throws DBConnectionError * @returns {BPromise} */ function markLoggedInTimestamp(userId) { return new BPromise(resolve => resolve()); }
/** * Send a follow up email * * @param {String} userId * @throws EmailError * @returns {BPromise} */ function sendEmail(userId) { return new BPromise(resolve => resolve()); }
/** * Generate a JWT token to send to the client * * @param {Object} user * @returns {BPromise<String>} */ function generateJWT(user) { const token = 'DUMMY_JWT_TOKEN';
return new BPromise(resolve => resolve(token)); }
|
一些值得注意的要点:
多余的外层变量
let user;
/* ... */ .then(fetchedUser => user = fetchedUser) /* ... */ .then(() => sendEmail(user.userId)) /* ... */
|
可知,user是一个全局变量,因为我需要在Promise链中使用它。如果不希望定义多余的外层变量,则需要在Promise链中的每一个函数中都返回user变量,这样做显然更加糟糕。
烦人的Promise链
/* ... */ BPromise.try(() => validateUserInput(req)) /* ... */
|
一个Promise链必须从Promise开始,但是validateUserInput函数并没有返回Promise,这时需要使用Bluebird。我也知道这样写比较奇怪…
讨厌的Bluebird
我在很多地方都使用了Bluebird,如果不用它的话,代码会更加臃肿。所谓DRY,即Don’t repeat yourself,我们可以使用Bluebird去尽量简化代码。但是,Bluebird是一个第三方依赖,如果出问题了怎么办?去掉Bluebird应该更好!
JavaScript太灵(gui)活(yi)了,出了BUG你也不知道,不妨接入Fundebug线上实时监控。
Async/Await示例
当我放弃Promise,使用Async/Await之后,代码是这样的:
const { WrongCredentialsError, DBConnectionError, EmailError } = require('./../errors');
/** * Emulate an Express.js route call as an example */ loginController({}, { json: response => console.log(response) }, null)
/** * * @param {Object} req * @param {Object} res * @param {Object} err * @returns {Void} */ async function loginController(req, res, err) { const { email, password } = req.email;
try { if (!email || !password) { throw new WrongCredentialsError(); }
const user = await fetchUserByEmail(email);
if (user.password !== hashPassword(req.password)) { throw new WrongCredentialsError(); }
await markLoggedInTimestamp(user.userId); await sendEmail(user.userId);
const token = await generateJWT(user);
res.json({ success: true, token });
} catch (err) { if (err instanceof WrongCredentialsError) { res.json({ success: false, error: 'Invalid email and/or password' }) } else if (err instanceof DBConnectionError || err instanceof EmailError) { res.json({ success: false, error: 'Unexpected error, please try again' }); } else { res.json({ success: false }) } } }
/** * Fetch a User from the DB by Email * * @throws WrongCredentialsError * @throws DBConnectionError * @returns {Promise} */ function fetchUserByEmail(email) { const user = { userId: 'DUMMY_ID', email: 'konmpar@gmail.com', password: 'DUMMY_PASSWORD_HASH' } return new Promise(resolve => resolve(user)); }
/** * Hash password * * @param {String} password * @returns {String} */ function hashPassword(password) { return password; }
/** * Mark a user's logged in timestamp * * @param {String} userId * @throws DBConnectionError * @returns {Promise} */ function markLoggedInTimestamp(userId) { return new Promise(resolve => resolve()); }
/** * Send a follow up email * * @param {String} userId * @throws EmailError * @returns {Promise} */ function sendEmail(userId) { return new Promise(resolve => resolve()); }
/** * Generate a JWT token to send to the client * * @param {Object} user * @returns {Promise<String>} */ function generateJWT(user) { const token = 'DUMMY_JWT_TOKEN';
return new Promise(resolve => resolve(token)); }
|
哈哈!!!
没有外层变量
现在,所有函数都在同一个作用域中调用,不再需要.then函数。因此,我们不再需要定义多余的全局变量,也不需要做多余的变量赋值。
没有多余的函数
Promise示例中的同步函数validateInput和comparePasswords的代码可以与异步函数写在一起,因此可以不再需要定义单独的函数,代码更少。
可读性更高
异步代码采用同步方式来写,同时减少了代码量,可读性大大提高。
不再需要Bluebird
原生的Promise可以替代Bluebird,且不再需要Bluebird的try方法了。
结论
作为程序员,我们应该努力完善代码。Async/Await可以带来很大好处,帮助我们写出可读性更高的代码。如果你坚持使用Promise,不妨看看如何在Promise链中共享变量?。
如果你对Async/Await感兴趣的话,可以看看这些博客:
版权声明:
转载时请注明作者Fundebug以及本文地址:
https://blog.fundebug.com/2018/01/31/a-real-async-await-example/
- Async/Await 学习与示例
参考:Async/await学习 es 7 提供了对 promise 对象的更好的操作,省去了很多丧心病狂的链式异步请求,promise 是回调地狱的福音,而 Async/Await 则是 promi ...
- [.NET] 利用 async & await 的异步编程
利用 async & await 的异步编程 [博主]反骨仔 [出处]http://www.cnblogs.com/liqingwen/p/5922573.html 目录 异步编程的简介 异 ...
- [.NET] 利用 async & await 进行异步 IO 操作
利用 async & await 进行异步 IO 操作 [博主]反骨仔 [出处]http://www.cnblogs.com/liqingwen/p/6082673.html 序 上次,博主 ...
- [C#] 走进异步编程的世界 - 开始接触 async/await
走进异步编程的世界 - 开始接触 async/await 序 这是学习异步编程的入门篇. 涉及 C# 5.0 引入的 async/await,但在控制台输出示例时经常会采用 C# 6.0 的 $&qu ...
- ASP.NET 中的 Async/Await 简介
本文转载自MSDN 作者:Stephen Cleary 原文地址:https://msdn.microsoft.com/en-us/magazine/dn802603.aspx 大多数有关 async ...
- EntityFramework 如何进行异步化(关键词:async·await·SaveChangesAsync·ToListAsync)
应用程序为什么要异步化?关于这个原因就不多说了,至于现有项目中代码异步化改进,可以参考:实际案例:在现有代码中通过async/await实现并行 这篇博文内容针对的是,EntityFramework ...
- 走进异步编程的世界 - 开始接触 async/await
[C#] 走进异步编程的世界 - 开始接触 async/await 走进异步编程的世界 - 开始接触 async/await 序 这是学习异步编程的入门篇. 涉及 C# 5.0 引入的 async ...
- ASP.NET 上的 Async/Await 简介
原文链接 大多数有关 async/await 的在线资源假定您正在开发客户端应用程序,但在服务器上有 async 的位置吗?可以非常肯定地回答“有”.本文是对 ASP.NET 上异步请求的概念性概述, ...
- Python PEP 492 中文翻译——协程与async/await语法
原文标题:PEP 0492 -- Coroutines with async and await syntax 原文链接:https://www.python.org/dev/peps/pep-049 ...
随机推荐
- wordpess设置回复可见
easy2hide 是一个不错的隐藏部分内容,评论后可见的插件,可在插件安装后台搜索 easy2hide 在线安装,或者在此下载 easy2hide. 在编辑文章的时候,切换到html文本编辑模式 测 ...
- 微信小程序的wx-charts插件-tab选项卡
微信小程序的wx-charts插件-tab选项卡 效果: //index.js var wxCharts = require('../../utils/wxcharts-min.js'); const ...
- 深入分析Java I/O的工作机制 (一)
此篇博客看至许令波的深入分析javaWeb内幕书籍, 此篇博客写的是自己看完之后理解的重点内容,加一些理解,希望对你有帮助. 1.Java的I/O类库的基本架构 先说一下什么是类库:可以说是类的集合, ...
- elasticsearch 摘要
看终于有人把Elasticsearch原理讲透了!, 后总结: 反向索引又叫倒排索引,是根据文章内容中的关键字建立索引. 搜索引擎原理就是建立反向索引 elasticsearch在Lucene的基础上 ...
- ajax实现异步前后台交互,模拟百度搜索框智能提示
1.什么是异步?在传统的网站项目中,填写一堆数据,最后点击提交,在点击提交的这一刻才实现数据提交,前后台交互.在你点击提交之前数据是没有提交到后台的.这样就会造成很大的不便.比如,我填了一大堆数据,结 ...
- vue android低版本 白屏问题 你是不是用了Object.assign ??
问题描述 在部分比较低版本的手机中,发现apk安装后白屏,但是大部分手机都能安装. 本人在使用android4.4时候,也是安装后打开白屏. 原因: 代码写法不兼容 this.user = Objec ...
- Mac 下使用svn
作为一个一直使用windows系统的人,还真不知道mac上的svn如何使用,偶然机会下现在需要. 查过后发现 mac 是自带svn的,在我装好xcode后,再安装 Command Line Tools ...
- asp.net MVC 5 路由 Routing
ASP.NET MVC ,一个适用于WEB应用程序的经典模型 model-view-controller 模式.相对于web forms一个单一的整块,asp.net mvc是由连接在一起的各种代码层 ...
- 可以用py库: pyautogui (自动测试模块,模拟鼠标、键盘动作)来代替pyuserinput
PyAutoGUI 是一个人性化的跨平台 GUI 自动测试模块 pyUserInput模块安装前需要安装pywin32和pyHook模块.(想要装的看https://www.cnblogs.com/m ...
- 在Mac OSX上配置Appium+Android自动化测试环境
前提准备 开始正文之前,你需要准备好一些基本条件: 1.安装好Mac OSX 操作系统的设备 2.能够访问中国局域网以外资源的方法(没有也行,但很痛苦) 3.已经安装好 homebrew 4.已经安装 ...