今天有同事問我下面這段程式碼是什麼意思:
var MyClass = function() { events.EventEmitter.call(this); // 這行是什麼意思? }; util.inherits(MyClass, events.EventEmitter); // 還有這行?
我也不是很明白,於是研究了一下。下面是我的一些體會。
Christmas Trees和Errors
如果你寫過JavaScript或NodeJS程式碼,你也許會對callback地獄深有體會。每次當你進行非同步呼叫時,按照callback的契約,你需要傳一個function作為回撥函式,function的第一個引數則預設為接收的error。這是一個非常棒的約定,不過仍然存在兩個小問題:
1. 每次回撥過程中都需要檢查是否存在error - 這很煩人
2. 每一次的回撥都會使程式碼向右縮排,如果回撥的層級很多,則我們的程式碼看起來就像聖誕樹一樣:
此外,如果每個回撥都是一個匿名函式幷包含大量的程式碼,那麼維護這樣的程式碼將會使人抓狂。
你會怎麼做呢?
其實有許多方法都可以解決這些問題,下面我將提供三種不同方式編寫的程式碼用於說明它們之間的區別。
1. 標準回撥函式
2. Event Emitter
3. Promises
我建立了一個簡單的類"User Registration",用於將email儲存到資料庫併傳送。在每個示例中,我都假設save操作成功,傳送email操作失敗。
1)標準回撥函式
前面已經提過,NodeJS對回撥函式有一個約定,那就是error在前,回撥在後。在每一次回撥中,如果出現錯誤,你需要將錯誤丟擲並截斷餘下的回撥操作。
########################### registration.js ################################# var Registration = function () { if (!(this instanceof Registration)) return new Registration(); var _save = function (email, callback) { setTimeout(function(){ callback(null); }, 20); }; var _send = function (email, callback) { setTimeout(function(){ callback(new Error("Failed to send")); }, 20); }; this.register = function (email, callback) { _save(email, function (err) { if (err) return callback(err); _send(email, function (err) { callback(err); }); }); }; }; module.exports = Registration; ########################### app.js ################################# var Registration = require('./registration.js'); var registry = new Registration(); registry.register("john@example.com", function (err) { console.log("done", err); });
大部分時候我還是傾向於使用標準回撥函式。如果你覺得你的程式碼結構看起來很清晰,那麼我不認為"Christmas Tree"會對我產生太多的困擾。回撥中的error檢查會有點煩人,不過程式碼看起來很簡單。
2)Event Emitter
在NodeJS中,有一個內建的庫叫EventEmitter非常不錯,它被廣泛應用到NodeJS的整個系統中。
你可以建立emitter的一個例項,不過更常見的做法是從emitter繼承,這樣你可以訂閱從特定物件上產生的事件。
最關鍵的是我們可以將事件連線起來變成一種工作流如“當email被成功儲存之後就立刻傳送”。
此外,名為error的事件有一種特殊的行為,當error事件沒有被任何物件訂閱時,它將在控制檯列印堆疊跟蹤資訊並退出整個程式。也就是說,未處理的errors會使整個程式崩掉。
########################### registration.js ################################# var util = require('util'); var EventEmitter = require('events').EventEmitter; var Registration = function () { //call the base constructor EventEmitter.call(this); var _save = function (email, callback) { this.emit('saved', email); }; var _send = function (email, callback) { //or call this on success: this.emit('sent', email); this.emit('error', new Error("unable to send email")); }; var _success = function (email, callback) { this.emit('success', email); }; //the only public method this.register = function (email, callback) { this.emit('beginRegistration', email); }; //wire up our events this.on('beginRegistration', _save); this.on('saved', _send); this.on('sent', _success); }; //inherit from EventEmitter util.inherits(Registration, EventEmitter); module.exports = Registration; ########################### app.js ################################# var Registration = require('./registration.js'); var registry = new Registration(); //if we didn't register for 'error', then the program would close when an error happened registry.on('error', function(err){ console.log("Failed with error:", err); }); //register for the success event registry.on('success', function(){ console.log("Success!"); }); //begin the registration registry.register("john@example.com");
你可以看到上面的程式碼中幾乎沒有什麼巢狀,而且我們也不用像之前那樣在回撥函式中去檢查errors。如果有錯誤發生,程式將丟擲錯誤資訊並繞過餘下的註冊過程。
3)Promises
這裡有大量關於promises的說明,如promises-spec, common-js等等。下面是我的理解。
Promises是一種約定,它使得對巢狀回撥和錯誤的管理看起來更加優雅。例如非同步呼叫一個save方法,我們不用給它傳遞迴調函式,該方法將返回一個promise物件。這個物件包含一個then方法,我們將callback函式傳遞給它以完成回撥函式的註冊。
據我所知,人們傾向於使用標準回撥函式的形式來編寫程式碼,然後使用如deferred的庫來將這些回撥函式轉換成promise的形式。這正是我在這裡要做的。
######################### app.js ############################ var Registration = require('./registration.js'); var registry = new Registration(); registry.register("john@example.com") .then( function () { console.log("Success!"); }, function (err) { console.log("Failed with error:", err); } ); ######################### registration.js ############################ var deferred = require('deferred'); var Registration = function () { //written as conventional callbacks, then converted to promises var _save = deferred.promisify(function (email, callback) { callback(null); }); var _send = deferred.promisify(function (email, callback) { callback(new Error("Failed to send")); }); this.register = function (email, callback) { //chain two promises together and return a promise return _save(email) .then(_send); }; }; module.exports = Registration;
promise消除了程式碼中的巢狀呼叫以及像EventEmitter那樣傳遞error。這裡我列出了它們之間的一些區別:
EventEmitter
- 需要引用util和events庫,這兩個庫已經包含在NodeJS中
- 你需要將自己的類從EventEmitter繼承
- 如果error未處理則會丟擲執行時異常
- 支援釋出/訂閱模式
Promise
- 使整個回撥形成一個鏈式結構
- 需要庫的支援來將回撥函式轉換成promise的形式,如deferred或Q
- 會帶來更多的開銷,所以可能會稍微有點慢
- 不支援釋出/訂閱模式
原文地址:http://www.joshwright.com/tips/javascript-christmas-trees-promises-and-event-emitters