Cho-Ching's Blog

[Promise]使用Bluebird

Promise用起來實在太開心了! 目前我拿來取代async的非同步工作流程。

什麼是Promise

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 chaining

傳回這個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 這張圖片解釋的很清楚: promise chain

Wow! 我們做到了跟callback一樣的功能, 但是更容易閱讀!

More

Error Handling in Node.js

Promise/A+

promisejs.org

promise nuggets

Unit testing express middleware

You're Missing the Point of Promises

使用 Promise 模式,寫出簡單易懂的 marionette test case

<< 回到文章列表