123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869 |
- {
- "name": "eventproxy",
- "description": "An implementation of task/event based asynchronous pattern.",
- "homepage": "https://github.com/JacksonTian/eventproxy",
- "keywords": [
- "event",
- "task-base",
- "event machine",
- "nested callback terminator"
- ],
- "author": {
- "name": "Jackson Tian",
- "email": "shyvo1987@gmail.com"
- },
- "contributors": [
- {
- "name": "Jackson Tian",
- "email": "shyvo1987@gmail.com"
- },
- {
- "name": "fengmk2",
- "email": "fengmk2@gmail.com"
- }
- ],
- "dependencies": {
- "debug": "0.7.2"
- },
- "devDependencies": {
- "mocha": "*",
- "mocha-phantomjs": "*",
- "component": "*",
- "blanket": "*",
- "pedding": "*",
- "chai": "*",
- "travis-cov": "*"
- },
- "scripts": {
- "test": "make test-all"
- },
- "config": {
- "blanket": {
- "pattern": "eventproxy/lib",
- "data-cover-flags": {
- "debug": false
- }
- },
- "travis-cov": {
- "threshold": 97
- }
- },
- "repository": {
- "type": "git",
- "url": "git://github.com/JacksonTian/eventproxy.git"
- },
- "version": "0.2.7",
- "main": "index.js",
- "directories": {
- "doc": "doc",
- "test": "test"
- },
- "license": "MIT",
- "readme": "EventProxy [![Build Status](https://secure.travis-ci.org/JacksonTian/eventproxy.png)](http://travis-ci.org/JacksonTian/eventproxy) [![NPM version](https://badge.fury.io/js/eventproxy.png)](http://badge.fury.io/js/eventproxy) [English Doc](https://github.com/JacksonTian/eventproxy/blob/master/README_en.md)\n======\n\n> 这个世界上不存在所谓回调函数深度嵌套的问题。 —— [Jackson Tian](http://weibo.com/shyvo)\n\n> 世界上本没有嵌套回调,写得人多了,也便有了`}}}}}}}}}}}}`。 —— [fengmk2](http://fengmk2.github.com)\n\n* API文档: [API Documentation](http://html5ify.com/eventproxy/api.html)\n* jscoverage: [97%](http://html5ify.com/eventproxy/coverage.html)\n* 源码注解:[注解文档](http://html5ify.com/eventproxy/eventproxy.html)\n\n\nEventProxy 仅仅是一个很轻量的工具,但是能够带来一种事件式编程的思维变化。有几个特点:\n\n1. 利用事件机制解耦复杂业务逻辑\n2. 移除被广为诟病的深度callback嵌套问题\n3. 将串行等待变成并行等待,提升多异步协作场景下的执行效率\n4. 友好的Error handling\n5. 无平台依赖,适合前后端,能用于浏览器和Node.js\n6. 兼容CMD,AMD以及CommonJS模块环境\n\n现在的,无深度嵌套的,并行的\n\n```js\nvar ep = EventProxy.create(\"template\", \"data\", \"l10n\", function (template, data, l10n) {\n _.template(template, data, l10n);\n});\n\n$.get(\"template\", function (template) {\n // something\n ep.emit(\"template\", template);\n});\n$.get(\"data\", function (data) {\n // something\n ep.emit(\"data\", data);\n});\n$.get(\"l10n\", function (l10n) {\n // something\n ep.emit(\"l10n\", l10n);\n});\n```\n\n过去的,深度嵌套的,串行的。\n\n```js\nvar render = function (template, data) {\n _.template(template, data);\n};\n$.get(\"template\", function (template) {\n // something\n $.get(\"data\", function (data) {\n // something\n $.get(\"l10n\", function (l10n) {\n // something\n render(template, data, l10n);\n });\n });\n});\n```\n## 安装\n### Node用户\n通过NPM安装即可使用:\n\n```bash\n$ npm install eventproxy\n```\n\n调用:\n\n```js\nvar EventProxy = require('eventproxy');\n```\n\n### Component\n\n```bash\n$ component install JacksonTian/eventproxy\n```\n\n### 前端用户\n以下示例均指向Github的源文件地址,您也可以[下载源文件](https://raw.github.com/JacksonTian/eventproxy/master/lib/eventproxy.js)到你自己的项目中。整个文件注释全面,带注释和空行,一共约500行。为保证EventProxy的易嵌入,项目暂不提供压缩版。用户可以自行采用Uglify、YUI Compressor或Google Closure Complier进行压缩。\n\n#### 普通环境\n在页面中嵌入脚本即可使用:\n\n```html\n<script src=\"https://raw.github.com/JacksonTian/eventproxy/master/lib/eventproxy.js\"></script>\n```\n使用:\n\n```js\n// EventProxy此时是一个全局变量\nvar ep = new EventProxy();\n```\n\n#### SeaJS用户\nSeaJS下只需配置别名,然后`require`引用即可使用。\n\n```js\n// 配置\nseajs.config({\n alias: {\n eventproxy: 'https://raw.github.com/JacksonTian/eventproxy/master/lib/eventproxy.js'\n }\n});\n// 使用\nseajs.use(['eventproxy'], function (EventProxy) {\n // TODO\n});\n// 或者\ndefine('test', function (require, exports, modules) {\n var EventProxy = require('eventproxy');\n});\n```\n\n#### RequireJS用户\nRequireJS实现的是AMD规范。\n\n```js\n// 配置路径\nrequire.config({\n paths: {\n eventproxy: \"https://raw.github.com/JacksonTian/eventproxy/master/lib/eventproxy\"\n }\n});\n// 使用\nrequire([\"eventproxy\"], function (EventProxy) {\n // TODO\n});\n```\n## 异步协作\n### 多类型异步协作\n此处以页面渲染为场景,渲染页面需要模板、数据。假设都需要异步读取。\n\n```js\nvar ep = new EventProxy();\nep.all('tpl', 'data', function (tpl, data) {\n // 在所有指定的事件触发后,将会被调用执行\n // 参数对应各自的事件名\n});\nfs.readFile('template.tpl', 'utf-8', function (err, content) {\n ep.emit('tpl', content);\n});\ndb.get('some sql', function (err, result) {\n ep.emit('data', result);\n});\n```\n\n`all`方法将handler注册到事件组合上。当注册的多个事件都触发后,将会调用handler执行,每个事件传递的数据,将会依照事件名顺序,传入handler作为参数。\n#### 快速创建\nEventProxy提供了`create`静态方法,可以快速完成注册`all`事件。\n\n```js\nvar ep = EventProxy.create('tpl', 'data', function (tpl, data) {\n // TODO\n});\n```\n\n以上方法等效于\n\n```js\nvar ep = new EventProxy();\nep.all('tpl', 'data', function (tpl, data) {\n // TODO\n});\n```\n\n### 重复异步协作\n此处以读取目录下的所有文件为例,在异步操作中,我们需要在所有异步调用结束后,执行某些操作。\n\n```js\nvar ep = new EventProxy();\nep.after('got_file', files.length, function (list) {\n // 在所有文件的异步执行结束后将被执行\n // 所有文件的内容都存在list数组中\n});\nfor (var i = 0; i < files.length; i++) {\n fs.readFile(files[i], 'utf-8', function (err, content) {\n // 触发结果事件\n ep.emit('got_file', content);\n });\n}\n```\n\n`after`方法适合重复的操作,比如读取10个文件,调用5次数据库等。将handler注册到N次相同事件的触发上。达到指定的触发数,handler将会被调用执行,每次触发的数据,将会按触发顺序,存为数组作为参数传入。\n\n### 持续型异步协作\n此处以股票为例,数据和模板都是异步获取,但是数据会持续刷新,视图会需要重新刷新。\n\n```js\nvar ep = new EventProxy();\nep.tail('tpl', 'data', function (tpl, data) {\n // 在所有指定的事件触发后,将会被调用执行\n // 参数对应各自的事件名的最新数据\n});\nfs.readFile('template.tpl', 'utf-8', function (err, content) {\n ep.emit('tpl', content);\n});\nsetInterval(function () {\n db.get('some sql', function (err, result) {\n ep.emit('data', result);\n });\n}, 2000);\n```\n\n`tail`与`all`方法比较类似,都是注册到事件组合上。不同在于,指定事件都触发之后,如果事件依旧持续触发,将会在每次触发时调用handler,极像一条尾巴。\n\n\n## 基本事件\n通过事件实现异步协作是EventProxy的主要亮点。除此之外,它还是一个基本的事件库。携带如下基本API\n\n- `on`/`addListener`,绑定事件监听器\n- `emit`,触发事件\n- `once`,绑定只执行一次的事件监听器\n- `removeListener`,移除事件的监听器\n- `removeAllListeners`,移除单个事件或者所有事件的监听器\n\n为了照顾各个环境的开发者,上面的方法多具有别名。\n\n- YUI3使用者,`subscribe`和`fire`你应该知道分别对应的是`on`/`addListener`和`emit`。\n- jQuery使用者,`trigger`对应的方法是`emit`,`bind`对应的就是`on`/`addListener`。\n- `removeListener`和`removeAllListeners`其实都可以通过别名`unbind`完成。\n\n所以在你的环境下,选用你喜欢的API即可。\n\n更多API的描述请访问[API Docs](http://html5ify.com/eventproxy/api.html)。\n\n## 异常处理\n在异步方法中,实际上,异常处理需要占用一定比例的精力。在过去一段时间内,我们都是通过额外添加`error`事件来进行处理的,代码大致如下:\n\n```js\nexports.getContent = function (callback) {\n var ep = new EventProxy();\n ep.all('tpl', 'data', function (tpl, data) {\n // 成功回调\n callback(null, {\n template: tpl,\n data: data\n });\n });\n // 侦听error事件\n ep.bind('error', function (err) {\n // 卸载掉所有handler\n ep.unbind();\n // 异常回调\n callback(err);\n });\n fs.readFile('template.tpl', 'utf-8', function (err, content) {\n if (err) {\n // 一旦发生异常,一律交给error事件的handler处理\n return ep.emit('error', err);\n }\n ep.emit('tpl', content);\n });\n db.get('some sql', function (err, result) {\n if (err) {\n // 一旦发生异常,一律交给error事件的handler处理\n return ep.emit('error', err);\n }\n ep.emit('data', result);\n });\n};\n```\n\n代码量因为异常的处理,一下子上去了很多。在这里EventProxy经过很多实践后,我们根据我们的最佳实践提供了优化的错误处理方案。\n\n```js\nexports.getContent = function (callback) {\n var ep = new EventProxy();\n ep.all('tpl', 'data', function (tpl, data) {\n // 成功回调\n callback(null, {\n template: tpl,\n data: data\n });\n });\n // 添加error handler\n ep.fail(callback);\n\n fs.readFile('template.tpl', 'utf-8', ep.done('tpl'));\n db.get('some sql', ep.done('data'));\n};\n```\n\n上述代码优化之后,业务开发者几乎不用关心异常处理了。代码量降低效果明显。 \n这里代码的转换,也许有开发者并不放心。其实秘诀在`fail`方法和`done`方法中。\n\n### 神奇的fail\n\n```js\nep.fail(callback);\n// 由于参数位相同,它实际是\nep.fail(function (err) {\n callback(err);\n});\n\n// 等价于\nep.bind('error', function (err) {\n // 卸载掉所有handler\n ep.unbind();\n // 异常回调\n callback(err);\n});\n```\n\n`fail`方法侦听了`error`事件,默认处理卸载掉所有handler,并调用回调函数。\n\n### 神奇的done\n\n```js\nep.done('tpl');\n// 等价于\nfunction (err, content) {\n if (err) {\n // 一旦发生异常,一律交给error事件的handler处理\n return ep.emit('error', err);\n }\n ep.emit('tpl', content);\n}\n```\n\n在Node的最佳实践中,回调函数第一个参数一定会是一个`error`对象。检测到异常后,将会触发`error`事件。剩下的参数,将触发事件,传递给对应handler处理。\n\n#### done也接受回调函数\n`done`方法除了接受事件名外,还接受回调函数。如果是函数时,它将剔除第一个`error`对象(此时为`null`)后剩余的参数,传递给该回调函数作为参数。该回调函数无需考虑异常处理。\n\n```js\nep.done(function (content) {\n // 这里无需考虑异常\n // 手工emit\n ep.emit('someevent', newcontent);\n});\n```\n\n当然手工emit的方式并不太好,我们更进一步的版本:\n\n```js\nep.done('tpl', function (tpl) {\n // 将内容更改后,返回即可\n return tpl.trim();\n});\n```\n\n#### 注意事项\n如果`emit`需要传递多个参数时,`ep.done(event, fn)`的方式不能满足需求,还是需要`ep.done(fn)`,进行手工`emit`多个参数。\n\n### 神奇的group\n`fail`除了用于协助`all`方法完成外,也能协助`after`中的异常处理。另外,在`after`的回调函数中,结果顺序是与用户`emit`的顺序有关。为了满足返回数据按发起异步调用的顺序排列,`EventProxy`提供了`group`方法。\n\n```js\nvar ep = new EventProxy();\nep.after('got_file', files.length, function (list) {\n // 在所有文件的异步执行结束后将被执行\n // 所有文件的内容都存在list数组中,按顺序排列\n});\nfor (var i = 0; i < files.length; i++) {\n fs.readFile(files[i], 'utf-8', ep.group('got_file'));\n}\n```\n`group`秉承`done`函数的设计,它包含异常的传递。同时它还隐含了对返回数据进行编号,在结束时,按顺序返回。\n\n```js\nep.group('got_file');\n// 约等价于\nfunction (err, data) {\n if (err) {\n return ep.emit('error', err);\n }\n ep.emit('got_file', data);\n};\n```\n\n当回调函数的数据还需要进行加工时,可以给`group`带上回调函数,只要在操作后将数据返回即可:\n\n```js\nep.group('got_file', function (data) {\n // some code\n return data;\n});\n```\n\n### 异步事件触发: emitLater && doneLater\n\n在node中,`emit`方法是同步的,EventProxy中的`emit`,`trigger`等跟node的风格一致,也是同步的。看下面这段代码,可能眼尖的同学一下就发现了隐藏的bug: \n```js\nvar ep = EventProxy.create();\n\ndb.check('key', function (err, permission) {\n if (err) {\n return ep.emit('error', err);\n }\n ep.emit('check', permission);\n});\n\nep.once('check', function (permission) {\n permission && db.get('key', function (err, data) {\n if (err) {\n return ep.emit('error');\n }\n ep.emit('get', data);\n });\n});\n\nep.once('get', function (err, data) {\n if (err) {\n retern ep.emit('error', err);\n }\n render(data);\n});\n\nep.on('error', errorHandler);\n```\n\n没错,万一`db.check`的`callback`被同步执行了,在`ep`监听`check`事件之前,它就已经被抛出来了,后续逻辑没办法继续执行。尽管node的约定是所有的`callback`都是需要异步返回的,但是如果这个方法是由第三方提供的,我们没有办法保证`db.check`的`callback`一定会异步执行,所以我们的代码通常就变成了这样: \n\n```js\nvar ep = EventProxy.create();\n\nep.once('check', function (permission) {\n permission && db.get('key', function (err, data) {\n if (err) {\n return ep.emit('error');\n }\n ep.emit('get', data);\n });\n});\n\nep.once('get', function (err, data) {\n if (err) {\n retern ep.emit('error', err);\n }\n render(data);\n});\n\nep.on('error', errorHandler);\n\ndb.check('key', function (err, permission) {\n if (err) {\n return ep.emit('error', err);\n }\n ep.emit('check', permission);\n});\n```\n我们被迫把`db.check`挪到最后,保证事件先被监听,再执行`db.check`。`check`->`get`->`render`的逻辑,在代码中看起来变成了`get`->`render`->`check`。如果整个逻辑更加复杂,这种风格将会让代码很难读懂。 \n\n这时候,我们需要的就是 __异步事件触发__: \n\n```js\nvar ep = EventProxy.create();\n\ndb.check('key', function (err, permission) {\n if (err) {\n return ep.emitLater('error', err);\n }\n ep.emitLater('check', permission);\n});\n\nep.once('check', function (permission) {\n permission && db.get('key', function (err, data) {\n if (err) {\n return ep.emit('error');\n }\n ep.emit('get', data);\n });\n});\n\nep.once('get', function (err, data) {\n if (err) {\n retern ep.emit('error', err);\n }\n render(data);\n});\n\nep.on('error', errorHandler);\n```\n上面代码中,我们把`db.check`的回调函数中的事件通过`emitLater`触发,这样,就算`db.check`的回调函数被同步执行了,事件的触发也还是异步的,`ep`在当前事件循环中监听了所有的事件,之后的事件循环中才会去触发`check`事件。代码顺序将和逻辑顺序保持一致。 \n当然,这么复杂的代码,必须可以像`ep.done()`一样通过`doneLater`来解决: \n\n```js\nvar ep = EventProxy.create();\n\ndb.check('key', ep.doneLater('check'));\n\nep.once('check', function (permission) {\n permission && db.get('key', ep.done('get'));\n});\n\nep.once('get', function (data) {\n render(data);\n});\n\nep.fail(errorHandler);\n```\n最终呈现出来的,是一段简洁且清晰的代码。 \n\n\n## 注意事项\n- 请勿使用`all`作为业务中的事件名。该事件名为保留事件。\n- 异常处理部分,请遵循Node的最佳实践(回调函数首个参数为异常传递位)。\n\n## [贡献者们](https://github.com/JacksonTian/eventproxy/graphs/contributors)\n谢谢EventProxy的使用者们,享受EventProxy的过程,也给EventProxy回馈良多。\n\n```bash\n project : eventproxy\n repo age : 1 year, 10 months\n active : 58 days\n commits : 136\n files : 18\n authors : \n 123 Jackson Tian 90.4%\n 6 fengmk2 4.4%\n 4 dead-horse 2.9%\n 1 haoxin 0.7%\n 1 redky 0.7%\n 1 yaoazhen 0.7%\n\n```\n\n## License \n\n[The MIT License](https://github.com/JacksonTian/eventproxy/blob/master/MIT-License)。请自由享受开源。\n\n## 捐赠\n如果您觉得本模块对您有帮助,欢迎请作者一杯咖啡\n\n[![捐赠EventProxy](https://img.alipay.com/sys/personalprod/style/mc/btn-index.png)](https://me.alipay.com/jacksontian)\n\n\n[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/JacksonTian/eventproxy/trend.png)](https://bitdeli.com/free \"Bitdeli Badge\")\n\n",
- "readmeFilename": "README.md",
- "bugs": {
- "url": "https://github.com/JacksonTian/eventproxy/issues"
- },
- "_id": "eventproxy@0.2.7",
- "_from": "eventproxy@"
- }
|