假設一個業務場景:
通過rss地址,獲取rss並保存於文件,rss地址保存於文件中。
完成該場景的業務需要完成3個任務:
1.從文件中讀取rss地址。
2.獲取rss。
3.保存於文件。
最後將這三個任務進行整合。
准備:
存放rss地址的文件,address.txt。
http://programmer.csdn.net/rss_programmer.html
任務1:
讀取rss地址文件的內容並通過callback返回。
代碼如下:
var getRssAddress = function(path, callback) {
fs.readFile(path, {encoding: 'utf8'}, function (err, data) {
callback(err, data);
});
}
任務2:
通過rss地址get到rss,並通過callback返回錯誤或數據。
代碼如下:
var getRss = function(url, callback) {
var data = '';
http.get(url, function(res) {
res.on('data', function(chrunk) {
data += chrunk;
});
res.on('end', function() {
callback(null, data);
});
}).on('error', function(err) {
callback(err, null);
});
}
任務3:
將rss保存於文件並通過callback返回錯誤。
代碼如下:
var saveRss = function(data, callback) {
fs.writeFile('rss.txt', data, 'utf8', function(err) {
callback(err);
});
}
整合:
代碼如下:
getRssAddress('address.txt', function(err, data) {
if(err) {
console.log(err);
return;
}
getRss(data, function(err, data) {
if(err) {
console.log(err);
return;
}
saveRss(data, function(err) {
if(err) console.log(err);
});
});
});
上面的代碼是全異步處理,使用最常見的callback處理異步邏輯的返回,好處是標准寫法,大家都能容易接受;壞處是耦合性太強,處理異常麻煩,代碼不直觀,特別是處理業務邏輯復雜和處理任務多的場景,層層的callback會讓人眼冒金星,代碼難以維護。
Promise/A規范的實現之一when.js正是針對這樣的問題域。
讓我們來看一下改造後的代碼。
任務1:
代碼如下:
var getRssAddress = function(path) {
var deferred = when.defer();
fs.readFile(path, {encoding: 'utf8'}, function (err, data) {
if (err) deferred.reject(err);
deferred.resolve(data);
});
return deferred.promise;
}
任務2:
代碼如下:
var getRss = function(url) {
var deferred = when.defer();
var data = '';
http.get(url, function(res) {
res.on('data', function(chrunk) {
data += chrunk;
});
res.on('end', function() {
deferred.resolve(data);
});
}).on('error', function(err) {
deferred.reject(err);
});
return deferred.promise;
}
任務3:
代碼如下:
var saveRss = function(data) {
var deferred = when.defer();
fs.writeFile('rss.txt', data, 'utf8', function(err) {
if(err) deferred.reject(err);
deferred.resolve();
});
return deferred.promise;
}
整合:
代碼如下:
getRssAddress('address.txt')
.then(getRss)
.then(saveRss)
.catch(function(err) {
console.log(err);
});
解釋:
promise/A規范定義的“Deferred/Promise”模型就是“發布/訂閱者”模型,通過Deferred對象發布事件,可以是完成resolve事件,或者是失敗reject事件;通過Promise對象進行對應完成或失敗的訂閱。
在Promises/A規范中,每個任務都有三種狀態:默認(pending)、完成(fulfilled)、失敗(rejected)。
1.默認狀態可以單向轉移到完成狀態,這個過程叫resolve,對應的方法是deferred.resolve(promiseOrValue);
2.默認狀態還可以單向轉移到失敗狀態,這個過程叫reject,對應的方法是deferred.reject(reason);
3.默認狀態時,還可以通過deferred.notify(update)來宣告任務執行信息,如執行進度;
4.狀態的轉移是一次性的,一旦任務由初始的pending轉為其他狀態,就會進入到下一個任務的執行過程中。
按照上面的代碼。
通過when.defer定義一個deferred對象。
var deferred = when.defer();
異步數據獲取成功後,發布一個完成事件。
deferred.resolve(data);
異步數據獲取失敗後,發布一個失敗事件。
deferred.reject(err);
並且返回Promise對象作為訂閱使用。
return deferred.promise;
訂閱是通過Promise對象的then方法進行完成/失敗/通知的訂閱。
getRssAddress('address.txt')
.then(getRss)
then有三個參數,分別是onFulfilled、onRejected、onProgress
promise.then(onFulfilled, onRejected, onProgress)
上一個任務被resolve(data),onFulfilled函數就會被觸發,data作為它的參數.
上一個任務被reject(reason),那麼onRejected就會被觸發,收到reason。
任何時候,onFulfilled和onRejected都只有其一可以被觸發,並且只觸發一次。
對於處理異常,when.js也提供了極其方便的方法,then能傳遞錯誤,多個任務串行執行時,我們可以只在最後一個then定義onRejected。也可以在最後一個then的後面調用catch函數捕獲任何一個任務的異常。
如此寫法簡單明了。
代碼如下:
getRssAddress('address.txt')
.then(getRss)
.then(saveRss)
.catch(function(err) {
console.log(err);
});
Promise給異步編程帶來了巨大的方便,可以讓我們專注於單個任務的實現而不會陷入金字塔厄運,以上代碼僅僅是基本使用,when.js提供的功能遠遠不止本文提到的這些,具體參照官方API。