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-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
Christmas Trees, Promises和Event Emitters的更多相关文章
- [LeetCode] Cut Off Trees for Golf Event 为高尔夫赛事砍树
You are asked to cut off trees in a forest for a golf event. The forest is represented as a non-nega ...
- [Swift]LeetCode675. 为高尔夫比赛砍树 | Cut Off Trees for Golf Event
You are asked to cut off trees in a forest for a golf event. The forest is represented as a non-nega ...
- LeetCode - Cut Off Trees for Golf Event
You are asked to cut off trees in a forest for a golf event. The forest is represented as a non-nega ...
- LeetCode 675. Cut Off Trees for Golf Event
原题链接在这里:https://leetcode.com/problems/cut-off-trees-for-golf-event/description/ 题目: You are asked to ...
- [LeetCode] 675. Cut Off Trees for Golf Event 为高尔夫赛事砍树
You are asked to cut off trees in a forest for a golf event. The forest is represented as a non-nega ...
- 675. Cut Off Trees for Golf Event
// Potential improvements: // 1. we can use vector<int> { h, x, y } to replace Element, sortin ...
- codeforces 1283D. Christmas Trees(bfs)
链接: https://codeforces.com/contest/1283/problem/D 题意:给定n个不同的整数点,让你找m个不同的整数点,使得这m个点到到这n个点最小距离之和最小. 思路 ...
- Gulp自动化构建的基本使用
Study Notes 本博主会持续更新各种前端的技术,如果各位道友喜欢,可以关注.收藏.点赞下本博主的文章. Gulp 用自动化构建工具增强你的工作流程! gulp 将开发流程中让人痛苦或耗时的任务 ...
- 【POJ3710】Christmas Game (博弈-树上的删边问题)
[题目] Description Harry and Sally were playing games at Christmas Eve. They drew some Christmas trees ...
随机推荐
- Oracle的tnsnames.ora配置(PLSQL Developer)
首先打开tnsnames.ora的存放目录,一般为D:\app\Administrator\product\11.2.0\client_1\network\admin,就看安装具体位置了. 步骤阅读 ...
- 服务升级中的zookeeper
服务升级中zookeeper可以管理服务中的配置以及作为leader选举以及分布式事务等, 在这次主要用于配置管理,关于配置管理主要设计如下,通过zookeeper管理配置项,通过 管理界面来管理数据 ...
- Logback配置连接
logback 简介 logback 常用配置详解(一)<configuration> and <logger> logback 常用配置详解(二)<appender&g ...
- 【oracle】union、union all、intersect、minus 的用法及区别
一.union与union all 首先建两个view create or replace view test_view_1 as as c from dual union as c from dua ...
- 关于php-fpm子进程达到上限并且浏览器访问显示504错误
今天上班遇到一个非常奇怪的事情,公司监控服务器之前都是在正常运行,使用nginx+php-fpm,并且监控服务器上部署这其他部门在使用的几个站点,从早上上班开始发现监控显示页面打不开,各种查找原因,最 ...
- BZOJ4597: [Shoi2016]随机序列
Description 你的面前有N个数排成一行.分别为A1, A2, … , An.你打算在每相邻的两个 Ai和 Ai+1 间都插入一个加号或者 减号或者乘号.那么一共有 3^(n-1) 种可能的表 ...
- C# DataTable的Select()方法不支持 != 判断
异常描述: 用户代码未处理 System.Data.SyntaxErrorException HResult=-2146232032 Message=无法解释位置 23 的标记“!”. Source= ...
- SpringMVC4 实例
之前我先创建普通web项目,再直接添加maven框架.结果springMVC框架一直不能正确的访问控制器. 但通过以下方式创建maven web项目就能获得正确的架构路径. 1.新建一个maven项目 ...
- [转]定位占用oracle数据库cpu过高的sql
今天在吃饭的时候我的朋友的数据库出现了问题,cpu占用率为97%,当我看到这个问题的时候我就想到了或许是sql导致的此问题,由于忍不住吃饭,暂时没有帮他看这个问题,这是我饭后自己模拟的故障,进行的分析 ...
- laravel中TokenMismatchException异常处理
在使用post或者put等方法请求时,有时会报TokenMismatchException in VerifyCsrfToken.php line 67错误.原因是laravel默认开启了防CSRF. ...