一个真实的Async/Await示例

译者按: 经过真实的代码示例感觉Async/Await的力量。javascript

为了保证可读性,本文采用意译而非直译。另外,本文版权归原做者全部,翻译仅用于学习。java

既然Node.js 8已经LTS了,我想你们是时候试一试Async/Await特性了,真的很好用!它能够帮助咱们用同步的方式写异步代码,极大地提升了代码的可读性。在过去的2年时间里,Promise给咱们带来了很多便利,同时也让咱们有一些失望。node

这边博客,我将介绍一个真实的代码示例,它是一个REST API的controller。经过展现咱们如何从Promise切换到async/await,你讲可以体会到Async/Await的神奇之处!程序员

Promise示例

下面是个人工做项目中真实的Controller代码:json

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

一些值得注意的要点:promise

多余的外层变量

let user;

/* ... */
.then(fetchedUser => user = fetchedUser)
/* ... */
.then(() => sendEmail(user.userId))
/* ... */

可知,user是一个全局变量,由于我须要在Promise链中使用它。若是不但愿定义多余的外层变量,则须要在Promise链中的每个函数中都返回user变量,这样作显然更加糟糕。异步

烦人的Promise链

/* ... */
BPromise.try(() => validateUserInput(req))
/* ... */

一个Promise链必须从Promise开始,可是validateUserInput函数并无返回Promise,这时须要使用Bluebird。我也知道这样写比较奇怪…async

讨厌的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示例中的同步函数validateInputcomparePasswords的代码能够与异步函数写在一块儿,所以能够再也不须要定义单独的函数,代码更少。

可读性更高

异步代码采用同步方式来写,同时减小了代码量,可读性大大提升。

再也不须要Bluebird

原生的Promise能够替代Bluebird,且再也不须要Bluebird的try方法了。

结论

做为程序员,咱们应该努力完善代码。Async/Await能够带来很大好处,帮助咱们写出可读性更高的代码。若是你坚持使用Promise,不妨看看如何在Promise链中共享变量?

若是你对Async/Await感兴趣的话,能够看看这些博客:


 

相关文章
相关标签/搜索