初始JavaScript Promises之二


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

異步編程中的錯誤處理

人性的、理想的也正如很多編程語言中已經實現的錯誤處理方式應該是這樣:

try {
    var val = JSON.parse(fs.readFileSync("file.json"));
}catch(SyntaxError e) {//json語法錯誤
    console.error("不符合json格式");
}catch(Error e) {//其它類型錯誤
    console.error("無法讀取文件")
}

 

很遺憾,JavaScript並不支持上述方式,於是“聰明的猴子”很可能寫出下面的代碼:

try {
    //code
}catch(e) {
    if( e instanceof SyntaxError) {
        //handle
    }else {
        //handle  
    }
}

 

相信沒人會喜歡第二段代碼,不過傳統的JavaScript也只能幫你到這里了。

上面的代碼是同步模式,異步模式中的錯誤處理又是如何呢?

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中你應該盡量避免使用同步方法。

仔細比較第一段和第三段的代碼的差異會發現,如此簡單的代碼竟然用了三次縮進!如果再加入其它異步操作,邂逅callback hell是必然的了。


使用Promise進行錯誤處理

假設fs.readFileAsync是fs.readFile的Promise版本,這意味着什么呢,不妨回憶一下:

  • fs.readFileAsync方法的返回結果是一個Promise對象

  • fs.readFileAsync方法的返回結果擁有一個then方法

  • 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使用。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM