百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术资源 > 正文

Christmas Trees, Promises和Event Emitters

moboyou 2025-07-18 19:21 13 浏览

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

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
  • 会带来更多的开销,所以可能会稍微有点慢
  • 不支持发布/订阅模式

相关推荐

Excel技巧:SHEETSNA函数一键提取所有工作表名称批量生产目录

首先介绍一下此函数:SHEETSNAME函数用于获取工作表的名称,有三个可选参数。语法:=SHEETSNAME([参照区域],[结果方向],[工作表范围])(参照区域,可选。给出参照,只返回参照单元格...

Excel HOUR函数:“小时”提取器_excel+hour函数提取器怎么用

一、函数概述HOUR函数是Excel中用于提取时间值小时部分的日期时间函数,返回0(12:00AM)到23(11:00PM)之间的整数。该函数在时间数据分析、考勤统计、日程安排等场景中应用广泛。语...

Filter+Search信息管理不再难|多条件|模糊查找|Excel函数应用

原创版权所有介绍一个信息管理系统,要求可以实现:多条件、模糊查找,手动输入的内容能去空格。先看效果,如下图动画演示这样的一个效果要怎样实现呢?本文所用函数有Filter和Search。先用filter...

FILTER函数介绍及经典用法12:FILTER+切片器的应用

EXCEL函数技巧:FILTER经典用法12。FILTER+切片器制作筛选按钮。FILTER的函数的经典用法12是用FILTER的函数和切片器制作一个筛选按钮。像左边的原始数据,右边想要制作一...

office办公应用网站推荐_office办公软件大全

以下是针对Office办公应用(Word/Excel/PPT等)的免费学习网站推荐,涵盖官方教程、综合平台及垂直领域资源,适合不同学习需求:一、官方权威资源1.微软Office官方培训...

WPS/Excel职场办公最常用的60个函数大全(含卡片),效率翻倍!

办公最常用的60个函数大全:从入门到精通,效率翻倍!在职场中,WPS/Excel几乎是每个人都离不开的工具,而函数则是其灵魂。掌握常用的函数,不仅能大幅提升工作效率,还能让你在数据处理、报表分析、自动...

收藏|查找神器Xlookup全集|一篇就够|Excel函数|图解教程

原创版权所有全程图解,方便阅读,内容比较多,请先收藏!Xlookup是Vlookup的升级函数,解决了Vlookup的所有缺点,可以完全取代Vlookup,学完本文后你将可以应对所有的查找难题,内容...

批量查询快递总耗时?用Excel这个公式,自动计算揽收到签收天数

批量查询快递总耗时?用Excel这个公式,自动计算揽收到签收天数在电商运营、物流对账等工作中,经常需要统计快递“揽收到签收”的耗时——比如判断某快递公司是否符合“3天内送达”的服务承...

Excel函数公式教程(490个实例详解)

Excel函数公式教程(490个实例详解)管理层的财务人员为什么那么厉害?就是因为他们精通excel技能!财务人员在日常工作中,经常会用到Excel财务函数公式,比如财务报表分析、工资核算、库存管理等...

Excel(WPS表格)Tocol函数应用技巧案例解读,建议收藏备用!

工作中,经常需要从多个单元格区域中提取唯一值,如体育赛事报名信息中提取唯一的参赛者信息等,此时如果复制粘贴然后去重,效率就会很低。如果能合理利用Tocol函数,将会极大地提高工作效率。一、功能及语法结...

Excel中的SCAN函数公式,把计算过程理清,你就会了

Excel新版本里面,除了出现非常好用的xlookup,Filter公式之外,还更新一批自定义函数,可以像写代码一样写公式其中SCAN函数公式,也非常强大,它是一个循环函数,今天来了解这个函数公式的计...

Excel(WPS表格)中多列去重就用Tocol+Unique组合函数,简单高效

在数据的分析和处理中,“去重”一直是绕不开的话题,如果单列去重,可以使用Unique函数完成,如果多列去重,如下图:从数据信息中可以看到,每位参赛者参加了多项运动,如果想知道去重后的参赛者有多少人,该...

Excel(WPS表格)函数Groupby,聚合统计,快速提高效率!

在前期的内容中,我们讲了很多的统计函数,如Sum系列、Average系列、Count系列、Rank系列等等……但如果用一个函数实现类似数据透视表的功能,就必须用Groupby函数,按指定字段进行聚合汇...

Excel新版本,IFS函数公式,太强大了!

我们举一个工作实例,现在需要计算业务员的奖励数据,右边是公司的奖励标准:在新版本的函数公式出来之前,我们需要使用IF函数公式来解决1、IF函数公式IF函数公式由三个参数组成,IF(判断条件,对的时候返...

Excel不用函数公式数据透视表,1秒完成多列项目汇总统计

如何将这里的多组数据进行汇总统计?每组数据当中一列是不同菜品,另一列就是该菜品的销售数量。如何进行汇总统计得到所有的菜品销售数量的求和、技术、平均、最大、最小值等数据?不用函数公式和数据透视表,一秒就...