初識JavaScript Promises之二

初始JavaScript Promises之二


上一篇咱們初步學習了JavaScript Promises,本篇將介紹Promise如何優雅地進行錯誤處理以及提高操做node.js風格1的異步方法的逼格,沒錯就是使用promisify2node

異步編程中的錯誤處理

人性的、理想的也正如不少編程語言中已經實現的錯誤處理方式應該是這樣: 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


使用Promise進行錯誤處理

假設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
})

上面的代碼沒有嵌套回調,和本文開始的第一段代碼的編寫模式也基本一致。

神奇的Promisify

注:

下面咱們看如何對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)


  1. node.js風格函數指的是這樣的一種異步函數,它接受的最後一個參數是異步操做完成以後的回調函數,這個回調函數的第一個參數接受執行錯誤的Error對象,第二個參數接受成功返回值)。

  2. promisify大概的意思就是根據一個node.js風格的異步方法生成另外一個等價的Promise風格的方法(這個方法返回值是一個Promise,其它形參與原方法相同除了沒有最後一個回調函數),這個名詞我最先是看到bluebird使用。

相關文章
相關標籤/搜索