Node.js async functions 最佳實踐
Node.js async functions 最佳實踐
從 Node.js 7.6 開始, Node.js 採用了支援 async functions
的新版 V8 引擎。2017年10月31日, Node.js 8 正式成為長期支援的版本,我們沒有理由不在程式碼中使用 async functions
了。在這篇文章中,我會簡單地向你說明什麼是 async functions
以及它們是如何改變我們編寫 Node.js 應用的方式的。
什麼是 async functions
?
async functions
能讓你像寫同步程式碼一樣編寫基於 Promise
( Promise-based
) 的程式碼。當你用 async
關鍵字定義了一個函式,你就可以在該函式內部使用 await
關鍵字。當這個 async function
被呼叫時,它會返回一個 Promise
。當這個 async function
返回一個值,被返回的 Promise
的狀態會變為 fufilled
,若 async function
丟擲一個錯誤, Promise
的狀態變為 rejected
。
await
關鍵字用來等待一個 Promise
被解決( resolved
)並返回結果( the fulfilled value
)。如果傳遞給 await
關鍵字的值不是一個 Promise
,那麼它會被轉換成一個 resolved Promise
。
const rp = require( 'request-promise' );
async function main() {
const result = await rp( 'https://google.com' );
const twenty = await 20;
// sleeeeeeeeping for a second ?
await new Promise( resolve => {
setTimeout( resolve, 1000 );
} );
return result;
}
main.then( console.log ).catch( console.error );
遷移到 async functions
如果你的 Node 應用已經在使用 Promise
,那就只需要開始 await
你的 Promise
,而不是像以往一樣把它們連結起來。
如果你的應用是使用 callback
構建的,那向 async functions
的遷移就應該逐步進行。你可以在新增新功能的時候使用這個新技術。如果你不得不使用之前的程式碼,那就簡單地用 Promise
把它們包裝起來。
可以使用內建的 util.promisify
方法來完成這件事情:
const util = require( 'util' );
const { readFile } = require( 'fs' );
const readFileAsync = util.promisify( readFile );
async function main() {
const result = await readFileAsync( '.gitignore' );
return result;
}
main.then( console.log ).catch( console.error );
async functions
的最佳實踐
在 express
中使用 async functions
由於 express
對 Promise
的支援非常好,在 express
中使用 asyncs functions
非常簡單:
const express = require( 'express' );
const app = express();
app.get( '/', async ( request, response ) => {
// awaiting Promise here
// if you just wait a single promise, you could simply return with it,
// no need to await for it
const result = await getContent();
response.send( result );
} );
app.listen( process.env.PORT );
上面的例子有一個嚴重的問題,如果 Promise
被 reject
, express
的路由處理函式會被掛起,因為沒有任何處理錯誤的行為。
為了解決這個問題,你需要把你的非同步處理函式放在一個能處理異常的函式中:
const awaitHandlerFactory = ( middleware ) => {
return async ( req, res, next ) => {
try {
await middleware( req, res, next );
} catch ( err ) {
next( err );
}
};
}
// and use it this way:
app.get( '/', awaitHandlerFactory( async ( request, response ) => {
const result = await getContent();
response.send( result );
} ) );
並行執行
想象你在做類似的事情,一個操作需要兩個輸入,一個來自資料庫,另一個來自外部服務:
async function main() {
const user = await Users.fetch( userId );
const product = await Products.fetch( productId );
await makePurchase( user, product );
}
在這個例子中,會發生如下事件:
- 你的程式碼會先獲取
user
資訊, - 然後獲取
product
資訊, - 最後完成購買
如你所見,你可以同時做前兩件事情,因為它們之間並沒有依賴關係。你可以通過 Promise.all
方法來做這件事:
async function main() {
const [ user, product ] = await Promise.all( [
Users.fetch( userId );
Products.fetch( productId );
] );
await makePurchase( user, product );
}
在另外一些情況下,你可能只需要最先被 resolve
的 Promise
的結果,在上面的例子中,你可以使用 Promise.race
方法。
錯誤處理
考慮下面的程式碼示例:
async function main() {
await new Promise( ( resolve, reject ) => {
reject( new Error( '?' ) );
} );
}
main.then( console.log );
在執行這個程式碼段時,你會在你的終端中看到類似這樣的資訊:
(node:69738) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): Error : ?
(node:69738) [DEP0018] DeprecationWarning: Unhandled promise rejections deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
在比較新的版本的 Node.js 中,如果 Promise rejections
沒有被處理,它會讓整個 Node
程式掛掉。正因為如此,在必要的時候,你應該使用 try-catch
塊:
const util = require( 'util' );
async function main() {
try {
await new Promise( ( resolve, reject ) => {
reject new Error( '?' );
} );
} catch ( err ) {
// handle error case
// maybe throwing is okay depending on your use-case
}
};
main.then( console.log ).catch( console.error );
但是,使用 try-catch
塊可能會隱藏一些重要的異常,比如你希望被丟擲的系統錯誤。
更復雜的控制流
async 是最早的關於 Node.js 非同步控制流的庫之一,它是由 Caolan McMahon 建立的。它提供一些 asynchronous helpers
,比如:
mapLimit
,filterLimit
,concatLimit
,priorityQueue
.
如果你不想為了實現同樣的邏輯而重複造輪子,而且你也希望使用一個月下載量達到5千萬、久經實踐驗證的庫,你可以在使用 async functions
的時候結合 util.promisify
利用上述的方法:
const util = require( 'util' );
const async = require( 'async' );
const numbers = [ 1, 2, 3, 4, 5 ];
mapLimitAsync = util.promisify( async.mapLimit );
async function main() {
return await mapLimitAsync( numbers, 2, ( number, done ) => {
setTimeout( function () {
done( null, number * 2 )
}, 100 );
} );
}
main.then( console.log ).catch( console.error );
相關文章
- 5個async/await最佳實踐AI
- Node.js 服務連線 MongoDB 處理最佳實踐Node.jsMongoDB
- 編寫 Node.js Rest API 的 10 個最佳實踐Node.jsRESTAPI
- Node.js 最佳實踐 —— 如何在 2018 年成為更好的 Node.js 開發者Node.js
- Pika最佳實踐
- Flutter 最佳實踐Flutter
- MongoDB 最佳實踐MongoDB
- 《.NET最佳實踐》
- Django 最佳實踐Django
- metaq最佳實踐
- Iptables 最佳實踐 !
- Java最佳實踐Java
- Kafka最佳實踐Kafka
- Log最佳實踐
- SnapKit 最佳實踐APK
- MacBook 最佳實踐Mac
- viewport 最佳實踐View
- ViewPager最佳實踐Viewpager
- OpenResty最佳實踐REST
- PHP最佳實踐PHP
- MongoDB最佳實踐MongoDB
- JDBC 最佳實踐JDBC
- JavaScript 最佳實踐JavaScript
- Node.js 中流操作實踐Node.js
- restful api最佳實踐RESTAPI
- MongoDB最佳安全實踐MongoDB
- springDataJpa 最佳實踐Spring
- App瘦身最佳實踐APP
- OpenResty 最佳實踐 (2)REST
- KeyPath 最佳實踐
- RocketMQ的最佳實踐MQ
- Java null最佳實踐JavaNull
- flutter + getx 最佳實踐Flutter
- [筆記]最佳實踐筆記
- RESTful API 最佳實踐RESTAPI
- mysqldump的最佳實踐MySql
- OpenResty 最佳實踐 (1)REST
- Rocketmq原理&最佳實踐MQ