上一篇咱們初步學習了JavaScript Promises,本篇將介紹Promise如何優雅地進行錯誤處理以及提高操做node.js風格1的異步方法的逼格,沒錯就是使用promisify2。 node
人性的、理想的也正如不少編程語言中已經實現的錯誤處理方式應該是這樣: mysql
try { var val = JSON.parse(fs.readFileSync("file.json")); }catch(SyntaxError e) {//json語法錯誤 console.error("不符合json格式"); }catch(Error e) {//其它類型錯誤 console.error("沒法讀取文件") }
很遺憾,JavaScript並不支持上述方式,因而「聰明的猴子」極可能寫出下面的代碼: git
try { //code }catch(e) { if( e instanceof SyntaxError) { //handle }else { //handle } }
相信沒人會喜歡第二段代碼,不過傳統的JavaScript也只能幫你到這裏了。 es6
上面的代碼是同步模式,異步模式中的錯誤處理又是如何呢? github
fs.readFile('file.json', 'utf8', function(err, data){ if(err){ console.error("沒法讀取文件") }else{ try{ var json = JSON.parese(data) }catch(e){ console.error("不符合json格式"); } } })
友情提醒:在node.js中你應該儘可能避免使用同步方法。 redis
仔細比較第一段和第三段的代碼的差別會發現,如此簡單的代碼居然用了三次縮進!若是再加入其它異步操做,邂逅callback hell
是必然的了。 sql
假設fs.readFileAsync是fs.readFile的Promise版本,這意味着什麼呢,不妨回憶一下: mongodb
fs.readFileAsync方法的返回結果是一個Promise對象 編程
fs.readFileAsync方法的返回結果擁有一個then方法 json
fs.readFileAsync方法接受參數與fs.readFile一致,除了最後一個回調函數
返回Promise對象意味着,執行fs.readFileAsync並不會當即執行異步操做,而是經過調用其then方法來執行,then方法接受的回調函數用於處理正確返回結果。因此使用fs.readFileAsync的使用方式以下:
//Promise版本 fs.readFileAsync('file.json', 'utf8').then(function(data){ console.log(data) })
OK,讓咱們繼續錯誤處理這個話題。因爲Promises/A+標準對Promise對象只規定了惟一的then方法,沒有專門針對catch或者error的方法,咱們將繼續使用bluebird。
// 帶錯誤處理的Promise版本 fs.readFileAsync('file.json', 'utf8').then(function(data){ console.log(data) }).catch(SyntaxError, function(e){ //code here }).catch(function(e){ //code here })
上面的代碼沒有嵌套回調,和本文開始的第一段代碼的編寫模式也基本一致。
注:
下面咱們看如何對fs.readFileAsync方法進行promisify,依然是使用bluebird。
var Promise = require('bluebird') fs.readFileAsync = Promise.promisify(fs.readFie, fs)
怎麼樣,就是如此簡單!對於bluebird它還有一個更強大的方法,那就是promisify的高級版本 promisifyAll
,好比:
var Promise = require('bluebird') Promise.promisifyAll(fs)
執行完上面的代碼以後,fs對象下全部的異步方法都會對應的生成一個Promise版本方法,好比fs.readFile對應fs.readFileAsync,fs.mkdir對應fs.mkdirAsync,以此類推。
另外要注意的就是,Promise版本的函數除了最後一個參數(回調函數),其它參數與原函數均一一對應,調用的時候別忘了傳遞原有的參數。
對fs的promisification
還不能令我知足,我須要更神奇的魔法:
// redis var Promise = require("bluebird"); Promise.promisifyAll(require("redis")); // mongoose var Promise = require("bluebird"); Promise.promisifyAll(require("mongoose")); // mongodb var Promise = require("bluebird"); Promise.promisifyAll(require("mongodb")); // mysql var Promise = require("bluebird"); Promise.promisifyAll(require("mysql/lib/Connection").prototype); Promise.promisifyAll(require("mysql/lib/Pool").prototype); // request var Promise = require("bluebird"); Promise.promisifyAll(require("request")); // mkdir var Promise = require("bluebird"); Promise.promisifyAll(require("mkdirp")); // winston var Promise = require("bluebird"); Promise.promisifyAll(require("winston")); // Nodemailer var Promise = require("bluebird"); Promise.promisifyAll(require("nodemailer")); // pg var Promise = require("bluebird"); Promise.promisifyAll(require("pg")); // ...
少年,這下你顫抖了嗎?
注:若是你正在使用mongoose,除了bluebird你可能還須要mongoomise,它的優勢在於:
可以接受任意的Promise Library (Q/when.js/RSVP/bluebird/es6-promise等等)
可以對Model自定義靜態私有方法進行promisify,而bluebird.promisifyAll不支持
mongoomise + bluebird與僅使用bluebird性能相差無幾,可能更好。
咱們幣須網已經在生產環境中使用mongoomise + bluebird,目前爲止一切安好。
(未完待續 2014-07-15 23:40)
node.js風格函數指的是這樣的一種異步函數,它接受的最後一個參數是異步操做完成以後的回調函數,這個回調函數的第一個參數接受執行錯誤的Error對象,第二個參數接受成功返回值)。
promisify
大概的意思就是根據一個node.js風格的異步方法生成另外一個等價的Promise風格的方法(這個方法返回值是一個Promise,其它形參與原方法相同除了沒有最後一個回調函數),這個名詞我最先是看到bluebird使用。