eventProxyAPI(转)
EventProxy 仅仅是一个很轻量的工具,但是能够带来一种事件式编程的思维变化。有几个特点:
- 利用事件机制解耦复杂业务逻辑
- 移除被广为诟病的深度callback嵌套问题
- 将串行等待变成并行等待,提升多异步协作场景下的执行效率
- 友好的Error handling
- 无平台依赖,适合前后端,能用于浏览器和Node.js
- 兼容CMD,AMD以及CommonJS模块环境
现在的,无深度嵌套的,并行的
var ep = EventProxy.create("template", "data", "l10n", function (template, data, l10n) {
_.template(template, data, l10n);
}); $.get("template", function (template) {
// something
ep.emit("template", template);
});
$.get("data", function (data) {
// something
ep.emit("data", data);
});
$.get("l10n", function (l10n) {
// something
ep.emit("l10n", l10n);
});
过去的,深度嵌套的,串行的。
var render = function (template, data) {
_.template(template, data);
};
$.get("template", function (template) {
// something
$.get("data", function (data) {
// something
$.get("l10n", function (l10n) {
// something
render(template, data, l10n);
});
});
});
安装
Node用户
通过NPM安装即可使用:
$ npm install eventproxy
调用:
var EventProxy = require('eventproxy');
Component
$ component install JacksonTian/eventproxy
前端用户
以下示例均指向Github的源文件地址,您也可以下载源文件到你自己的项目中。整个文件注释全面,带注释和空行,一共约500行。为保证EventProxy的易嵌入,项目暂不提供压缩版。用户可以自行采用Uglify、YUI Compressor或Google Closure Complier进行压缩。
普通环境
在页面中嵌入脚本即可使用:
<script src="https://raw.github.com/JacksonTian/eventproxy/master/lib/eventproxy.js"></script>
使用:
// EventProxy此时是一个全局变量
var ep = new EventProxy();
SeaJS用户
SeaJS下只需配置别名,然后require
引用即可使用。
// 配置
seajs.config({
alias: {
eventproxy: 'https://raw.github.com/JacksonTian/eventproxy/master/lib/eventproxy.js'
}
});
// 使用
seajs.use(['eventproxy'], function (EventProxy) {
// TODO
});
// 或者
define('test', function (require, exports, modules) {
var EventProxy = require('eventproxy');
});
RequireJS用户
RequireJS实现的是AMD规范。
// 配置路径
require.config({
paths: {
eventproxy: "https://raw.github.com/JacksonTian/eventproxy/master/lib/eventproxy"
}
});
// 使用
require(["eventproxy"], function (EventProxy) {
// TODO
});
异步协作
多类型异步协作
此处以页面渲染为场景,渲染页面需要模板、数据。假设都需要异步读取。
var ep = new EventProxy();
ep.all('tpl', 'data', function (tpl, data) {
// 在所有指定的事件触发后,将会被调用执行
// 参数对应各自的事件名
});
fs.readFile('template.tpl', 'utf-8', function (err, content) {
ep.emit('tpl', content);
});
db.get('some sql', function (err, result) {
ep.emit('data', result);
});
all
方法将handler注册到事件组合上。当注册的多个事件都触发后,将会调用handler执行,每个事件传递的数据,将会依照事件名顺序,传入handler作为参数。
快速创建
EventProxy提供了create
静态方法,可以快速完成注册all
事件。
var ep = EventProxy.create('tpl', 'data', function (tpl, data) {
// TODO
});
以上方法等效于
var ep = new EventProxy();
ep.all('tpl', 'data', function (tpl, data) {
// TODO
});
重复异步协作
此处以读取目录下的所有文件为例,在异步操作中,我们需要在所有异步调用结束后,执行某些操作。
var ep = new EventProxy();
ep.after('got_file', files.length, function (list) {
// 在所有文件的异步执行结束后将被执行
// 所有文件的内容都存在list数组中
});
for (var i = 0; i < files.length; i++) {
fs.readFile(files[i], 'utf-8', function (err, content) {
// 触发结果事件
ep.emit('got_file', content);
});
}
after
方法适合重复的操作,比如读取10个文件,调用5次数据库等。将handler注册到N次相同事件的触发上。达到指定的触发数,handler将会被调用执行,每次触发的数据,将会按触发顺序,存为数组作为参数传入。
持续型异步协作
此处以股票为例,数据和模板都是异步获取,但是数据会持续刷新,视图会需要重新刷新。
var ep = new EventProxy();
ep.tail('tpl', 'data', function (tpl, data) {
// 在所有指定的事件触发后,将会被调用执行
// 参数对应各自的事件名的最新数据
});
fs.readFile('template.tpl', 'utf-8', function (err, content) {
ep.emit('tpl', content);
});
setInterval(function () {
db.get('some sql', function (err, result) {
ep.emit('data', result);
});
}, 2000);
tail
与all
方法比较类似,都是注册到事件组合上。不同在于,指定事件都触发之后,如果事件依旧持续触发,将会在每次触发时调用handler,极像一条尾巴。
基本事件
通过事件实现异步协作是EventProxy的主要亮点。除此之外,它还是一个基本的事件库。携带如下基本API
on
/addListener
,绑定事件监听器emit
,触发事件once
,绑定只执行一次的事件监听器removeListener
,移除事件的监听器removeAllListeners
,移除单个事件或者所有事件的监听器
为了照顾各个环境的开发者,上面的方法多具有别名。
- YUI3使用者,
subscribe
和fire
你应该知道分别对应的是on
/addListener
和emit
。 - jQuery使用者,
trigger
对应的方法是emit
,bind
对应的就是on
/addListener
。 removeListener
和removeAllListeners
其实都可以通过别名unbind
完成。
所以在你的环境下,选用你喜欢的API即可。
更多API的描述请访问API Docs。
异常处理
在异步方法中,实际上,异常处理需要占用一定比例的精力。在过去一段时间内,我们都是通过额外添加error
事件来进行处理的,代码大致如下:
exports.getContent = function (callback) {
var ep = new EventProxy();
ep.all('tpl', 'data', function (tpl, data) {
// 成功回调
callback(null, {
template: tpl,
data: data
});
});
// 侦听error事件
ep.bind('error', function (err) {
// 卸载掉所有handler
ep.unbind();
// 异常回调
callback(err);
});
fs.readFile('template.tpl', 'utf-8', function (err, content) {
if (err) {
// 一旦发生异常,一律交给error事件的handler处理
return ep.emit('error', err);
}
ep.emit('tpl', content);
});
db.get('some sql', function (err, result) {
if (err) {
// 一旦发生异常,一律交给error事件的handler处理
return ep.emit('error', err);
}
ep.emit('data', result);
});
};
代码量因为异常的处理,一下子上去了很多。在这里EventProxy经过很多实践后,我们根据我们的最佳实践提供了优化的错误处理方案。
exports.getContent = function (callback) {
var ep = new EventProxy();
ep.all('tpl', 'data', function (tpl, data) {
// 成功回调
callback(null, {
template: tpl,
data: data
});
});
// 添加error handler
ep.fail(callback); fs.readFile('template.tpl', 'utf-8', ep.done('tpl'));
db.get('some sql', ep.done('data'));
};
上述代码优化之后,业务开发者几乎不用关心异常处理了。代码量降低效果明显。 这里代码的转换,也许有开发者并不放心。其实秘诀在fail
方法和done
方法中。
神奇的fail
ep.fail(callback);
// 由于参数位相同,它实际是
ep.fail(function (err) {
callback(err);
}); // 等价于
ep.bind('error', function (err) {
// 卸载掉所有handler
ep.unbind();
// 异常回调
callback(err);
});
fail
方法侦听了error
事件,默认处理卸载掉所有handler,并调用回调函数。
神奇的done
ep.done('tpl');
// 等价于
function (err, content) {
if (err) {
// 一旦发生异常,一律交给error事件的handler处理
return ep.emit('error', err);
}
ep.emit('tpl', content);
}
在Node的最佳实践中,回调函数第一个参数一定会是一个error
对象。检测到异常后,将会触发error
事件。剩下的参数,将触发事件,传递给对应handler处理。
done也接受回调函数
done
方法除了接受事件名外,还接受回调函数。如果是函数时,它将剔除第一个error
对象(此时为null
)后剩余的参数,传递给该回调函数作为参数。该回调函数无需考虑异常处理。
ep.done(function (content) {
// 这里无需考虑异常
// 手工emit
ep.emit('someevent', newcontent);
});
当然手工emit的方式并不太好,我们更进一步的版本:
ep.done('tpl', function (tpl) {
// 将内容更改后,返回即可
return tpl.trim();
});
注意事项
如果emit
需要传递多个参数时,ep.done(event, fn)
的方式不能满足需求,还是需要ep.done(fn)
,进行手工emit
多个参数。
神奇的group
fail
除了用于协助all
方法完成外,也能协助after
中的异常处理。另外,在after
的回调函数中,结果顺序是与用户emit
的顺序有关。为了满足返回数据按发起异步调用的顺序排列,EventProxy
提供了group
方法。
var ep = new EventProxy();
ep.after('got_file', files.length, function (list) {
// 在所有文件的异步执行结束后将被执行
// 所有文件的内容都存在list数组中,按顺序排列
});
for (var i = 0; i < files.length; i++) {
fs.readFile(files[i], 'utf-8', ep.group('got_file'));
}
group
秉承done
函数的设计,它包含异常的传递。同时它还隐含了对返回数据进行编号,在结束时,按顺序返回。
ep.group('got_file');
// 约等价于
function (err, data) {
if (err) {
return ep.emit('error', err);
}
ep.emit('got_file', data);
};
当回调函数的数据还需要进行加工时,可以给group
带上回调函数,只要在操作后将数据返回即可:
ep.group('got_file', function (data) {
// some code
return data;
});
异步事件触发: emitLater && doneLater
在node中,emit
方法是同步的,EventProxy中的emit
,trigger
等跟node的风格一致,也是同步的。看下面这段代码,可能眼尖的同学一下就发现了隐藏的bug:
var ep = EventProxy.create(); db.check('key', function (err, permission) {
if (err) {
return ep.emit('error', err);
}
ep.emit('check', permission);
}); ep.once('check', function (permission) {
permission && db.get('key', function (err, data) {
if (err) {
return ep.emit('error');
}
ep.emit('get', data);
});
}); ep.once('get', function (err, data) {
if (err) {
retern ep.emit('error', err);
}
render(data);
}); ep.on('error', errorHandler);
没错,万一db.check
的callback
被同步执行了,在ep
监听check
事件之前,它就已经被抛出来了,后续逻辑没办法继续执行。尽管node的约定是所有的callback
都是需要异步返回的,但是如果这个方法是由第三方提供的,我们没有办法保证db.check
的callback
一定会异步执行,所以我们的代码通常就变成了这样:
var ep = EventProxy.create(); ep.once('check', function (permission) {
permission && db.get('key', function (err, data) {
if (err) {
return ep.emit('error');
}
ep.emit('get', data);
});
}); ep.once('get', function (err, data) {
if (err) {
retern ep.emit('error', err);
}
render(data);
}); ep.on('error', errorHandler); db.check('key', function (err, permission) {
if (err) {
return ep.emit('error', err);
}
ep.emit('check', permission);
});
我们被迫把db.check
挪到最后,保证事件先被监听,再执行db.check
。check
->get
->render
的逻辑,在代码中看起来变成了get
->render
->check
。如果整个逻辑更加复杂,这种风格将会让代码很难读懂。
这时候,我们需要的就是 异步事件触发:
var ep = EventProxy.create(); db.check('key', function (err, permission) {
if (err) {
return ep.emitLater('error', err);
}
ep.emitLater('check', permission);
}); ep.once('check', function (permission) {
permission && db.get('key', function (err, data) {
if (err) {
return ep.emit('error');
}
ep.emit('get', data);
});
}); ep.once('get', function (err, data) {
if (err) {
retern ep.emit('error', err);
}
render(data);
}); ep.on('error', errorHandler);
上面代码中,我们把db.check
的回调函数中的事件通过emitLater
触发,这样,就算db.check
的回调函数被同步执行了,事件的触发也还是异步的,ep
在当前事件循环中监听了所有的事件,之后的事件循环中才会去触发check
事件。代码顺序将和逻辑顺序保持一致。 当然,这么复杂的代码,必须可以像ep.done()
一样通过doneLater
来解决:
var ep = EventProxy.create(); db.check('key', ep.doneLater('check')); ep.once('check', function (permission) {
permission && db.get('key', ep.done('get'));
}); ep.once('get', function (data) {
render(data);
}); ep.fail(errorHandler);
最终呈现出来的,是一段简洁且清晰的代码。
注意事项
- 请勿使用
all
作为业务中的事件名。该事件名为保留事件。 - 异常处理部分,请遵循Node的最佳实践(回调函数首个参数为异常传递位)。
本文转自https://github.com/JacksonTian/eventproxyAPI说明
eventProxyAPI(转)的更多相关文章
随机推荐
- hadoop/storm以及hive/hbase/pig区别整理
STORM与HADOOP的比较 对于一堆时刻在增长的数据,如果要统计,可以采取什么方法呢? 等数据增长到一定程度的时候,跑一个统计程序进行统计.适用于实时性要求不高的场景.如将数据导到HDFS,再运行 ...
- 5.3 存储器、I/O和配置读写请求TLP
本节讲述PCIe总线定义的各类TLP,并详细介绍这些TLP的格式.在这些TLP中,有些格式对于初学者来说较难理解.读者需要建立PCIe总线中与TLP相关的一些基本概念,特别是存储器读写相关的报文格式. ...
- linux虚拟机中安装mongodb
今天在linux虚拟机中安装了mongodb数据库,安装过程主要参照了网上的一片文档,大部分地方都没有多大区别,只是在某些细节上有所改变. 我参考的文档是:http://my.oschina.net/ ...
- web开发中对缓存的使用
很久没有发表随笔了,最近工作不是太忙,抽点时间 给大家谈谈缓存吧 ; 在我从事web开发的几年实践中 接触了缓存技术 也是比较多的,在最初的 项目当中 我们用到 hibernate 的 一二级缓存, ...
- 【JDK1.8】JUC.Lock综述
一.前言 前段时间结束了jdk1.8集合框架的源码阅读,在过年的这段时间里,一直在准备JUC(java.util.concurrent)的源码阅读.平时接触的并发场景开发并不很多,但是有网络的地方,就 ...
- 【BZOJ5093】图的价值(第二类斯特林数,组合数学,NTT)
[BZOJ5093]图的价值(第二类斯特林数,组合数学,NTT) 题面 BZOJ 题解 单独考虑每一个点的贡献: 因为不知道它连了几条边,所以枚举一下 \[\sum_{i=0}^{n-1}C_{n-1 ...
- [BZOJ1058][ZJOJ2007]报表统计
BZOJ Luogu 题目描述 Q的妈妈是一个出纳,经常需要做一些统计报表的工作.今天是妈妈的生日,小Q希望可以帮妈妈分担一些工作,作为她的生日礼物之一. 经过仔细观察,小Q发现统计一张报表实际上是维 ...
- luoguP2711 小行星
题目描述 星云中有n颗行星,每颗行星的位置是(x,y,z).每次可以消除一个面(即x,y或z坐标相等)的行星,但是由于时间有限,求消除这些行星的最少次数. 输入输出格式 输入格式: 第1行为小行星个数 ...
- [BZOJ1024] [SCOI2009] 生日快乐 (搜索)
Description windy的生日到了,为了庆祝生日,他的朋友们帮他买了一个边长分别为 X 和 Y 的矩形蛋糕.现在包括windy,一共有 N 个人来分这块大蛋糕,要求每个人必须获得相同面积的蛋 ...
- .NET Core使用skiasharp文字头像生成方案(基于docker发布)
一.问题背景 目前.NET Core下面针对于图像处理的库微软并没有集成,在.NET FrameWork下我们已经习惯使用System.Drawing类库做简单的图像处理,到了.NET Core下一脸 ...