Christmas Trees, Promises和Event Emitters

  今天有同事问我下面这段代码是什么意思:

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("[email protected]", 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("[email protected]");

  你可以看到上面的代码中几乎没有什么嵌套,而且我们也不用像之前那样在回调函数中去检查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("[email protected]")
  .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

时间: 2025-01-05 14:35:09

Christmas Trees, Promises和Event Emitters的相关文章

Codeforces Round #611 (Div. 3) D - Christmas Trees(BFS)

?? ?? ?? 题意:现在已知圣诞树在x位置,求人的位置,使每个人到达距离他最近的树的距离之和最小 安排人肯定从树旁边开始安排,树的距离为1的位置安排满之后继续安排下一层,因为线无线长,也没什么限制,就直接bfs map<int, bool> vis; queue<pair<int, int>> q; vector<int> v; int main() { int n, m;cin>>n>>m; for (int i = 0, x;

poj 3710 Christmas Game(树上的删边游戏)

Christmas Game Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 1967   Accepted: 613 Description Harry and Sally were playing games at Christmas Eve. They drew some Christmas trees on a paper: Then they took turns to cut a branch of a tre

POJ 3710 Christmas Game

Harry and Sally were playing games at Christmas Eve. They drew some Christmas trees on a paper: Then they took turns to cut a branch of a tree, and removed the part of the tree which had already not connected with the root. A step shows as follows: S

C#学习日记24----事件(event)

事件为类和类的实例提供了向外界发送通知的能力,实现了对象与对象之间的通信,如果定义了一个事件成员,表示该类型具有 1.能够在事件中注册方法 (+=操作符实现). 2.能够在事件中注销方法(-=操作符实现). 3.当事件被触发时注册的方法会被通知(事件内部维护了一个注册方法列表).委托(Delegate)是事件(event)的载体,要定义事件就的要有委托.  有关委托的内容请点击 委托(De... www.mafengwo.cn/event/event.php?iid=4971258www.maf

Awesome Swift

Awesome Swift A collaborative list of awesome Swift resources,inspired by awesome-python and listed on awesome-awesomeness. Feel free to contribute! Awesome Swift Demo Apps iOS Apple Watch OS X Dependency Managers Guides Editor Support Vim Libs Anima

Inverted sentences

And ever has it been that love knows not its own depth until the hour of separation. 除非临到了别离的时候,爱永远不会知道自己的深浅. 这是个倒装句,因为ever位于句首,句子要倒装,it是形式主语,has been是谓语,that从句是真正的主语从句,until后为状语 倒装句 在英语中,主语和谓语的语序通常是主语在前,谓语在后.这类语序被称为“自然语序”.但有时为了强调句子的某一部分,或由于其它诸如语法结构或

【React Native】源码分析之Native UI的封装和管理

??ReactNative作为使用React开发Native应用的新框架,随着时间的增加,无论是社区还是个人对她的兴趣与日递增.此文目的是希望和大家一起欣赏一下ReactNative的部分源码.阅读源码好处多多,让攻城狮更溜的开发ReactNative应用的同时,也能梳理RN项目的设计思路,增加自己的内功修为,^_^. ??好的,就让我们轻松的开始吧.此篇是以Android平台源码分析为主,分享Native UI的封装和管理,重点涉及react-native源码中com.facebook.rea

【HDU 3590】 PP and QQ (博弈-Anti-SG游戏,SJ定理,树上删边游戏)

PP and QQ Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 510    Accepted Submission(s): 256 Problem Description PP and QQ were playing games at Christmas Eve. They drew some Christmas trees on

LeetCode Problems List 题目汇总

No. Title Level Rate 1 Two Sum Medium 17.70% 2 Add Two Numbers Medium 21.10% 3 Longest Substring Without Repeating Characters Medium 20.60% 4 Median of Two Sorted Arrays Hard 17.40% 5 Longest Palindromic Substring Medium 20.70% 6 ZigZag Conversion Ea