JavaScript有很多槽點,嵌套回調怕是千夫所指。
很久之前,我一直使用async來處理JavaScript異步編程中的嵌套回調問題。當然我也大概的了解過一些其它旨在解決這些問題的類庫,諸如EventProxy、Jscex、StepJS、thenjs。
當我第一次看到Promises規范的時候,我根本無法理解它所帶來的好處。譬如每個初次學習Promises的人都見過如下的示例代碼:
//callbacks
function callback(err, value){
if(err){
// do something
return;
}
//do other things with value
}
//Promises
promise.then(function(value){
//do something with value
}, function(err){
//do other things with error
})
很難相信上面的代碼會讓人對Promises刮目相看。不過正如bluebird作者Petka所說,上面的代碼是
“最不誠實的比較”。所以我懇請你把類似的代碼從你的記憶中擦出吧。
不妨讓我們再回到async的討論上。async的問題在於它不能優雅地應對需求的變化,一旦業務邏輯有較大的變化,代碼結構會進行大幅度的調整,而Promises卻能夠輕松的應對這種變化。待時機適宜我會進行詳細的比較,首先讓我們開始快速地了解Promises。
Promises是什么
Promises象征着一個異步操作的最終結果。Promises交互主要通過它的then方法,then方法接受一個回調函數,這個回調函數接受執行成功的返回值或執行失敗的錯誤原因,錯誤原因一般是Error對象。需要注意的是,then方法執行的返回值是一個Promise對象,而then方法接受的回調函數的返回值則可以是任意的JavaScript對象,包括Promises。基於這種機制,Promise對象的鏈式調用就起作用了。
Promises的狀態
Promise對象有三種狀態:pending(初始狀態)、fulfilled(成功執行)、rejected(執行出錯)。pending狀態的Promise對象可以轉換到其它兩種狀態。
上面的文本不夠形象,不妨上些代碼來加深對Promises的認識。
注:由於主流的JavaScript環境(包括NodeJS)對Promises/A+標准的實現不太令人滿意,我的示例均使用了第三方類庫bluebird。
var fs = require('fs')
var Promise = require('bluebird')
//改造fs.readFile為Promise版本
var readFileAsync = function(path){
//返回一個Promise對象,初始狀態pending
return new Promise(function(fulfill, reject){
fs.readFile(path, 'utf8', function(err, content){
//由pending狀態進入rejected狀態
if(err)return reject(err)
//由pending狀態進入fulfilled狀態
return fulfill(content)
})
})
}
//開始使用,調用其then方法,回調接受執行成功的返回值
readFileAsync('./promise-1.js').then(function(content){
console.log(content)
})
看了上面的代碼以后,是不是覺得Promises其實並不復雜呢。
OK,我們繼續延續上面的代碼,來簡單比較一下傳統回調和Promises的使用上的差別:
/*
* 簡單比較一下傳統方式和Promises方式
* 需求:讀取兩個文件並打印內容
* */
//callbacks
fs.readFile('./promise-1.js', 'utf8', function(err, content1){
//嵌套一次
console.log('#', content1)
fs.readFile('./promise-1.js', 'utf8', function(err, content2){
//第二次嵌套
console.log('##', content2)
})
})
//Promises
readFileAsync('./promise-1.js').then(function(content1){
console.log('#', content1)
//這里返回一個Promise對象
return readFileAsync('./promiscuitye-1.js')
}).then(function(content2){
console.log('##', content2)
})
上面的代碼都沒有錯誤處理,這是一個后果很嚴重的壞習慣。不過今天我們的重點不在這里,而是分析上下兩段代碼的主要區別。
第一段代碼是傳統的嵌套回調,在第二次打印的時候已經使用了兩次縮進,而Promises鏈式調用then方法成功地避免了一次縮進(嵌套),維持了代碼結構的相對平坦。上面的代碼略顯簡陋,如果再加上錯誤處理,Promises毫無疑問將會大放光彩,有興趣請關注后續章節。
本章寫到這里就結束了,相信大家已經對Promises的有了一個初步認識。規范文檔往往很難理解,我沒有過多的描述規范,因為我相信代碼最能夠解釋一切。不過對規范文檔有興趣的可以自行閱讀參考鏈接。
最后我想強調的一點就是:Promises這種維持代碼結構平坦的魔力在業務邏輯復雜多變的情況下是非常有用的。
參考鏈接
未完待續(2014-06-28 00:5
