Promise用起來實在太開心了! 目前我拿來取代async的非同步工作流程。
Promise是一個javascript中處理非同步運算的一種手法。概念就是用一個promise(承諾)物件. 表示一個非同步運算的結果 。
一個promise物件有三種狀態: pending
(promise初始狀態, 還不知道結果), fulfilled
(resolved, 非同步運算結果成功), rejected
(非同步運算結果失敗)。
一但promise的結果出來了,不管結果是fulfilled或是rejected, 這個promise都無法被改變(immutable)。
假設我們要讀取一個檔案, 然後將檔案內容解析成JSON:
var fs = require('fs');
fs.readFile('file.js', 'utf8', function(err, val){
if (err) {
console.error('unable to read file.');
} else {
var result = JSON.parse(val);
console.log('result: ' + result);
}
});
這邊只處理了讀取檔案的錯誤, 若還要加上解決 JSON.parse(val)
的錯誤處理變這樣:
var fs = require('fs');
fs.readFile('file.js', 'utf8', function(err, val){
if (err) {
console.error('unable to read file.');
} else {
try{
var result = JSON.parse(val);
console.log('result: ' + result);
} catch (e) {
console.error('invalid json file.');
}
}
});
一連串的巢狀讓人覺得, 寫起來怎麼這麼囉唆, 有沒有將程式碼攤平, 讓整個程序看起來的"flow"(流程化)的好方法?
通常我們在Javscript講Pomise這個字, 通常我們指的是 CommonJS Promises/A+這個最小實作的規格, 那基於Promise/A規格實作的函式庫有很多, 我這邊用的是Bluebird。另外你也可能聽過 Q。我們比較少有機會手工打包建立一個Promise物件, 這裡利用現成的bluebird函式庫來幫我們達成。
$ npm install --save bluebird
Bluebird的Promisification將現有的nodejs API(promise-unware)轉成可以傳回promise的API(promise-returning):
var Promise = require("bluebird");
var readFile = Promise.promisify(require('fs'.readFile);
var myFile = fs.readFile('file.js', 'utf8');
這裡fs.readFile
method由原來的callback型式, 被轉成傳回一個promise物件。
如果這個引用函式庫有很多method都會用到, 那麼可以利用promisifyAll
這個method:
var fs = require("fs");
Promise.promisifyAll(fs);
fs.readFileAsync("file.js", "utf8").then(...)
注意, 這裡要使用fs.readFileAsync
來取代原來的fs.readFile
, 所有被promisfy的函式, 都會加上Async
的後綴。
傳回這個promise物件好處多多, 我們可以重複使用, 也可以利用promise的 .then()
method, 來串接我們的流程。當promise狀態為fullfilled或是rejected的時候執行對應的handler。
使用.catch(Handler)
就像是 .then(null, handler)
的簡便寫法, 只要有任何錯誤, 在整個.then-chain
裏面會丟給最近的.catch
handler處理。
使用 .catch(ErrorClass, Handler)
就好像C#或是Java的 try-catch-finally block一樣。Promise寫法的好處, 就是讓非同步程式的程式碼, 很像同步程式碼(try-catch-finally)的寫法與習慣。
上述例子接續改寫成如下:
myFile
.then(JSON.parse)
.then(function(result){
console.log('result: ' + result);
})
.catch(SyntaxError, function(e){
console.error('invalid json file.');
})
.catch(function(e){
console.error('unable to read file.');
});
.then
或是 .catch
都是傳回一個promise。
MDN 這張圖片解釋的很清楚:
Wow! 我們做到了跟callback一樣的功能, 但是更容易閱讀!
Unit testing express middleware
You're Missing the Point of Promises
使用 Promise 模式,寫出簡單易懂的 marionette test case