1. 程式人生 > >一個真實的Async/Await示例

一個真實的Async/Await示例

date 開始 val ceo jwt 實的 怎麽辦 都在 state

譯者按: 通過真實的代碼示例感受Async/Await的力量。

  • 原文: Async/await - A thorough example
  • 譯者: Fundebug

為了保證可讀性,本文采用意譯而非直譯。另外,本文版權歸原作者所有,翻譯僅用於學習。

既然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: [email protected]‘,
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: [email protected]‘,
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感興趣的話,可以看看這些博客:

  • 重構:從Promise到Async/Await
  • Async/Await替代Promise的6個理由
  • Async/Await是這樣簡化JavaScript代碼的

技術分享圖片

一個真實的Async/Await示例