Christmas Trees, Promises和Event Emitters
moboyou 2025-07-18 19:21 2 浏览
今天有同事问我下面这段代码是什么意思:
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
- 会带来更多的开销,所以可能会稍微有点慢
- 不支持发布/订阅模式
- 上一篇:圣诞节快到了,用python、turtle画棵圣诞树吧
- 已经是最后一篇了
相关推荐
- 圣诞快乐:用GaussDB T 绘制一颗圣诞树,兼论高斯数据库语法兼容
-
转眼就是圣诞的节日,祝大家节日快乐。用GaussDBT(也就是GaussDB100)绘制一棵圣诞树,纯国产,更喜庆。话不多说,上图:SQL如下:SELECTCASEWHENENMOTE...
- Christmas Trees, Promises和Event Emitters
-
今天有同事问我下面这段代码是什么意思:varMyClass=function{events.EventEmitter.call(this);//这行是什么意思?};util...
- 圣诞节快到了,用python、turtle画棵圣诞树吧
-
首先需要安装python环境,如果是新手建议百度一下,有很详细的教程哦,根据电脑的情况去下载对应的python安装包,使用的是python3.7.0的版本,除此之外还有python2,和python...
- Python的浪漫就是送你一桌面的圣诞树
-
python来画一个!或者搞个前端来画!开始教学这次我们用的是python中的turtle(海龟)库,是python中的一个内置库不需要额外安装创建窗口设置画笔绘制图形setup()函数,其中的参数为...
- Python画圣诞树,这波操作让我女友非常开心
-
文章目录1.方块圣诞树2.线条圣诞树3.豪华圣诞树这篇文章主要介绍了使用Python画了一棵圣诞树的实例代码,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友...
- 快圣诞节了,用Python 做圣诞树送给她,还愁找不到女朋友吗?
-
马上就是圣诞节了,先提前祝大家圣诞快乐!:christmas_tree::christmas_tree::christmas_tree:有人要说了,圣诞节是耶稣诞生的日子,我又不信基督教,有啥好庆祝的...
- 资产管理如何做,用Excel vba,很简单,你还等什么
-
资产管理在企事业单位都比较重视,特别是一些固定资产比较多的部门,十分需要一个详细的管理过程。以前曾经做过一个资产管理的Excel表格,完成了80%功能,一直感觉总不是十分完美。这次要做一个十分过得去的...
- 数据库是什么?数据库连接、管理与分析工具推荐!
-
一、数据库是什么?数据库是一种结构化的数据存储系统,用于有效地组织、存储和管理大量的数据。它是一个集中化的数据存储库,通常由一个或多个数据表组成,每个数据表包含多个行和列,用于存储特定类型的数据。数据...
- 不会写代码的业务人员,如何用Godata在一两周内解决真实业务痛点
-
案例1|某汽摩配件厂:7天把Excel“搬”成MES报工系统背景300人工厂,原来用6张Excel收集机台产量,统计员每天加班到9点。IT排期至少3个月,费用20W+...
- 产品经理提需求时要考虑的 15 个隐性需求
-
虽然世界充满未知的变化,但是有一些大的方向还是可以把握的,本文跟大家谈谈产品经理提需求时要考虑的15个隐性需求,enjoy~俗话说,计划赶不上变化快,无论需求文档做得如何细致,考虑得如何周全,总会...
- 一、XXL-JOB分布式任务调度平台——简介篇
-
官网https://www.xuxueli.com/开源代码https://gitee.com/xuxueli0323/xxl-job?_from=gitee_search概述XXL-JOB是一个轻量...
- 织梦后台如何使用sql语句给dedecms模板添加自定义属性
-
dedecms网站后台如何使用sql语句给dedecms模板添加自定义属性?织梦dedecms模板前台调用文章时经常会觉得属性不够用,我们可以自定义很多属性标签,这样前台调用时就可以做到非常精准,而添...
- 2025年主流显示接口全解析:HDMI、DP、USB-C 谁才是未来之选?
-
显示接口技术不断发展,目前主流的和曾经流行的显示接口标准较多,下面我为你全面盘点一下常见显示接口的标准,包括它们的用途、优缺点和使用场景:一、主流数字显示接口1.HDMI(High-Definiti...
- MoonTV:一个开箱即用、跨平台影视聚合播放器,畅享海量免费影视
-
这个项目和之前推荐过的项目有点类似,原理基本一致,而且该项目最下方引用处也说明了相关情况:如果你之前已经看过下面这篇文章并且搭建好了相关站点,则该项目对于你来说应该是轻车熟路了:LibreTV:自建一...
- 一周热门
- 最近发表
- 标签列表
-
- 外键约束 oracle (36)
- oracle的row number (32)
- 唯一索引 oracle (34)
- oracle in 表变量 (28)
- oracle导出dmp导出 (28)
- oracle两个表 (20)
- oracle 数据库 字符集 (20)
- oracle安装补丁 (19)
- matlab化简多项式 (20)
- 多线程的创建方式 (29)
- 多线程 python (30)
- java多线程并发处理 (32)
- 宏程序代码一览表 (35)
- c++需要学多久 (25)
- css class选择器用法 (25)
- css样式引入 (30)
- html5和css3新特性 (19)
- css教程文字移动 (33)
- php简单源码 (36)
- php个人中心源码 (25)
- php小说爬取源码 (23)
- 云电脑app源码 (22)
- html画折线图 (24)
- docker好玩的应用 (28)
- linux有没有pe工具 (34)