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("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-speccommon-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


本文转自Jaxu博客园博客,原文链接:http://www.cnblogs.com/jaxu/p/5645455.html,如需转载请自行联系原作者


相关文章
|
4月前
|
存储 JSON 开发框架
给开源大模型带来Function Calling、 Respond With Class
OpenAI 在他的多个版本的模型里提供了一个非常有用的功能叫 Function Calling,就是你传递一些方法的信息给到大模型,大模型根据用户的提问选择合适的方法,然后输出给你,你再来执行。
|
4月前
SyntaxError: await is only valid in async function
SyntaxError: await is only valid in async function
|
8月前
|
机器学习/深度学习 人工智能 自然语言处理
DualCor: Event Causality Extraction with Event Argument Correlations论文解读
事件因果关系识别(ECI)是事件因果关系理解的重要任务,其目的是检测两个给定文本事件之间是否存在因果关系。然而,ECI任务忽略了关键的事件结构和因果关系组件信息
53 0
|
8月前
|
机器学习/深度学习 算法 决策智能
【5分钟 Paper】(TD3) Addressing Function Approximation Error in Actor-Critic Methods
【5分钟 Paper】(TD3) Addressing Function Approximation Error in Actor-Critic Methods
|
8月前
throw new MongooseError(‘Mongoose.prototype.connect() no longer accepts a callback‘);
throw new MongooseError(‘Mongoose.prototype.connect() no longer accepts a callback‘);
77 1
|
8月前
|
存储 机器学习/深度学习 人工智能
PTPCG: Efficient Document-level Event Extraction via Pseudo-Trigger-aware Pruned Complete Graph论文解读
据我们所知,我们目前的方法是第一项研究在DEE中使用某些论元作为伪触发词的效果的工作,我们设计了一个指标来帮助自动选择一组伪触发词。此外,这种度量也可用于度量DEE中带标注触发词的质量。
88 1
|
8月前
|
人工智能 自然语言处理 BI
CLIP-Event: Connecting Text and Images with Event Structures 论文解读
视觉-语言(V+L)预训练模型通过理解图像和文本之间的对齐关系,在支持多媒体应用方面取得了巨大的成功。
47 0
|
8月前
|
存储 移动开发 自然语言处理
Document-Level event Extraction via human-like reading process 论文解读
文档级事件抽取(DEE)特别困难,因为它提出了两个挑战:论元分散和多事件。第一个挑战意味着一个事件记录的论元可能存在于文档中的不同句子中
58 0
|
前端开发
Uncaught (in promise) NavigationDuplicated: Avoided redundant navigation 问题
Uncaught (in promise) NavigationDuplicated: Avoided redundant navigation 问题
Uncaught (in promise) NavigationDuplicated: Avoided redundant navigation 问题
|
Linux C++
O_RDONLY/O_NOATIME undeclared (first use in this function
O_RDONLY/O_NOATIME undeclared (first use in this function
128 0
O_RDONLY/O_NOATIME undeclared (first use in this function