从事件驱动到observable的异步编程——PubSub+Promise+Rx的JS事件库

标签: JavaScript 代码 | 发表时间:2011-09-25 23:35 | 作者:Dexter.Yy Kejun
出处:http://www.limboy.com

你上当叻,虽然从外面看标题很有气势,传达出一种宏大叙事的赶脚,其实我只是刚刚把一个阿尔法城的JS模块提交到github,想顺便介绍一下,但我连API文档都懒得写,就别指望能深入浅出的讲一遍来龙去脉了⋯⋯

所以就直接帖几个前置阅读的链接罢!

这些潮流的外部起源:(技术也有外源论/exogenesis⋯⋯)
Twisted(Python的事件驱动异步引擎)里的Deferred模式
微软推崇的Reactive Extensions (Rx)

虽然我是微软黑但微软网站上的这两篇推介文章不错:
Understanding the Publish/Subscribe Pattern for Greater JavaScript Scalability
Asynchronous Programming in JavaScript with “Promises”
应该都有人翻译了,比如这个:infoQ: JavaScript异步编程的Promise模式

jQuery早就跟微软一个鼻孔出气了:
http://api.jquery.com/category/deferred-object/

CommonJS的Promises提案,照例又分了好多种ABCD神马的:
http://wiki.commonjs.org/wiki/Promises

假如你愿意这里还有一篇paper⋯⋯

阿尔法城的客户端程序里有一个叫作event的模块提供了以上提到的PubSub模式、Promise模式和部分Rx模式,可以算是OzJS的核心module。

就像名字一样,它的初衷是一个最基础最简洁的消息事件库,类似nodejs的EventEmitter。在项目实践中,我很早就注意到可以用统一的事件机制实现Twisted风格的API,为此需要能随时提取事件主题(本质上就是promise对象),后来又根据实际需求加入了能表示状态转移的触发器(enable/resolve)和“一次性”的侦听器(wait/then),最后实现了同时依赖多个异步事件(或promise)的语法工具,包括并发事件(when)和有先后顺序的事件流(follow和end)。所以这个模块不是基于自顶向下的设计,而是在逐步的实践、hack和验证中发展出来的,上面提到的各种模式词汇都是“事后美化”,我觉得大多数“设计模式”也是这样——是对实践方法的归纳和描述,而不是在实践中套用的“新技术”。


开始帖使用范例~

把Event实例单独定义为模块,承担应用各模块之间的消息传递:

  1. oz.def("notify", ["event"], function(Event){
  2.     return Event(); // 以下例子里省略def/require声明,继续沿用notify和Event这两个局部变量名
  3. });

为基础类生成独立的事件命名空间,不依赖应用级的全局事件:

  1. Dialog.prototype = {
  2.     init: function(opt){
  3.         this.event = Event();
  4.     },
  5.     update: function() {
  6.         this.updateSize();
  7.         this.updatePosition();
  8.         this.event.fire("update", [this]);
  9.         return this;
  10.     },

监听消息和解除监听:

  1. notify.bind("msg:A", function(msg){
  2.     a = msg;
  3.     notify.unbind("msg:A", arguments.callee);
  4. });

发送消息:

  1. setTimeout(function(){
  2.     notify.fire("msg:A", ["hey jude"]);
  3. }, 1000);

状态转移:

  1. $("#button1").click(function(e){
  2.     notify.resolve("button1:clicked", [this]);
  3.     notify.bind("button1:clicked", function(button){
  4.         // 按钮1已经点击过,所以立刻执行
  5.         button.style.color = 'black';
  6.     });
  7. });
  8. notify.bind("button1:clicked", function(button){
  9.     // 等待按钮1点击之后再执行
  10.     button.style.color = 'red';
  11. });

异步回调:

  1. var data = {
  2.     load: function(url){
  3.         $.getJSON(url, function(json){
  4.             if (json)
  5.                 notify.resolve("data:" + url, [json]);
  6.             else
  7.                 notify.reject("data:" + url);
  8.         });
  9.         return notify.promise("data:" + url);
  10.     }
  11. };
  12. data.load("jsonp_data_1.js").then(function(json){
  13.     // json callback
  14. }, function(){
  15.     // json error
  16. });

也可以用自己的promise对象:

  1. var promise = Event.Promise();
  2. $.ajax({
  3.     url: "jsonp_data_1.js",
  4.     success: function(json){
  5.         promise.resolve(json);
  6.         promise.fire("json loaded");
  7.     },
  8.     error: function(){
  9.         promise.reject();
  10.         promise.error("json error");
  11.     }
  12. });
  13. // fire和error都会执行bind的参数,resolve执行then和bind,所以bind的参数会被执行2次
  14. // 如果ajax请求在之前已经返回,则只有then或fail的参数会被执行(因为他们监听的是“状态改变”)
  15. promise.bind(function(){}).then(function(){}).fail(function(){});

事件流:

  1. notify.promise("data:jsonp_data_1.js").then(function(json){
  2.     setTimeout(function(){
  3.         notify.resolve("delay:1000", [+new Date(), json]);
  4.     }, 1000);
  5.     return notify.promise("delay:1000");
  6. }).follow().then(function(time, json){
  7.     setTimeout(function(){
  8.         console.log("[数据在3秒前加载成功]", json);
  9.     }, 2000);
  10. }).end().fail(function(msg){
  11.     return notify.promise("data:error").resolve([msg]);
  12. }).follow().then(function(){
  13.     console.log("[数据加载失败]", msg);
  14. });

避免多层的回调嵌套(“callback hell”):

  1. var fs = require("fs");
  2. fs.readFile(input, 'utf-8').then(function(err, data){
  3.     var beautifuldata = js_beautify(data, options);
  4.     // 需要修改readFile和writeFile传出promise对象
  5.     return fs.writeFile(output, beautifuldata);
  6. }).follow().then(function(err){
  7.     if (err)
  8.         throw err;
  9.     console.log('Success!');
  10. });

依赖多个并发事件:

  1. notify.when("msg:A", "msg:B", "jsonp:A", "jsonp:B") // when传出新的promise对象
  2.     .some(3) // 如果不调用some或any,默认为全部事件完成后再触发resolve
  3.     .then(function(){ // 已经取到3/4的数据,参数顺序跟when的参数顺序一样
  4.         console.warn("recieve 3/4", arguments);
  5.     });

静态方法Event.when接受promise参数,可以写出更复杂的依赖关系:

  1. Event.when(
  2.     notify.when("msg:A", "msg:B"),
  3.     notify.when("click:btn1", "clicked:btn2").any()
  4. ).then(function(args1, args2){
  5.     // 相当于:"msg:A" && "msg:B" && ( "click:btn1" || "clicked:btn2" )
  6.     console.warn("recieve all messages, click one button", arguments);
  7. });



测试demo:https://github.com/dexteryy/OzJS/blob/master/tests/test_event.html
可以在console里观察执行顺序⋯⋯

从这个测试页可以看出我连单元测试都懒得写⋯⋯

相关 [事件驱动 observable 异步] 推荐:

从事件驱动到observable的异步编程——PubSub+Promise+Rx的JS事件库

- Kejun - YY in Limbo 混沌海狂想
你上当叻,虽然从外面看标题很有气势,传达出一种宏大叙事的赶脚,其实我只是刚刚把一个阿尔法城的JS模块提交到github,想顺便介绍一下,但我连API文档都懒得写,就别指望能深入浅出的讲一遍来龙去脉了⋯⋯. 所以就直接帖几个前置阅读的链接罢. 这些潮流的外部起源:(技术也有外源论/exogenesis⋯⋯).

Redis事件驱动库结构

- zffl - NoSQLFan
本文翻译自Redis官方对事件驱动库的结构描述,英文原文点这里,由Day Day Up博客原创,文章写的时间已经比较长了,今天才被NoSQLFan挖出来,实属难得. 文章地址:blog.ddup.us. 这是一篇翻译文章,原文见这里. Redis实现了它自己的事件库. 要弄明白Redis事件库是如何工作的最好的方法就是弄明白Redis是如何使用它的.

再谈EDA事件驱动架构

- - 人月神话的BLOG
EDA事件驱动架构首先不是对于传统的面向业务流程,数据等各种架构模式的完全否定,而是解决传统架构下无法很好解决的一些问题. 传统模式里面更加关注业务流程和业务对象,而EDA模式下将更加关注在整个业务流程中的关键状态点,已经由关键状态点触发的有明确业务含义的业务事件. EDA架构的核心仍然是基于消息的发布订阅模式,消息的特定就是准实时,异步和彻底解耦.

反向Ajax,第5部分:事件驱动的Web开发

- Taozi - 译言-电脑/网络/数码科技
到目前为止你已经了解了创建通过事件来通信的组件,在本系列的最后一部分内容中,我们把事件驱动开发的原则应用到实践中,构建一个示例性的事件驱动web应用. 你可以下载本文中使用的源代码. 理想情况下,要充分体会本文的话,你应该对JavaScrpit和Java有一定的了解,并且要有一些web开发经验. 若要运行本文中的例子,你还需要最新版本的Maven和JDK(参见参考资料).

[译] 当提到 “事件驱动” 时,我们在说什么?

- - IT瘾-dev
文/Martin Fowler. 去年年底(译者注:2016年底),我和ThoughtWorks同事一起参加了一个研讨会,讨论“事件驱动”的本质. 在过去的几年里,我们构建的很多系统都大量使用了事件. 对于这些系统,人们常常赞誉有加,但批评的声音也不绝于耳. 我们的北美办公室组织了一次峰会,来自世界各地的ThoughtWorks资深开发者出席会议并分享了他们的想法.

微服务架构之事件驱动架构 - 简书

- -
为了解决传统的单体应用(Monolithic Application)在可扩展性、可靠性、适应性、高部署成本等方面的问题,许多公司(比如Amazon、eBay和NetFlix等)开始使用微服务架构(Microservice Architecture)构建自己的应用. 微服务(Microservices) 是一种软件架构风格 (Software Architecture Style),它是以专注于单一责任与功能的小型功能区块 (Small Building Blocks) 为基础,利用模组化的方式组合出复杂的大型应用程序,各功能区块使用与语言无关 (Language-Independent/Language agnostic) 的 API 集相互通讯.

事件驱动架构在 vivo 内容平台的实践

- - 掘金 架构
当下,随着微服务的兴起,容器化技术的发展,以及云原生、serverless 概念的普及,事件驱动再次引起业界的广泛关注. 所谓事件驱动的架构,也就是使用事件来实现跨多个服务的业务逻辑. 事件驱动架构是一种设计应用的软件架构和模型,可以最大程度减少耦合度,很好地扩展与适配不同类型的服务组件. 在这一架构里,当有重要事件发生时,比如更新业务数据,某个服务会发布事件,其它服务则订阅这些事件;当某一服务接收到事件就可以执行自己的业务流程,更新业务数据,同时发布新的事件触发下一步.

linux异步IO浅析

- Sepher - kouu's home
知道异步IO已经很久了,但是直到最近,才真正用它来解决一下实际问题(在一个CPU密集型的应用中,有一些需要处理的数据可能放在磁盘上. 预先知道这些数据的位置,所以预先发起异步IO读请求. 等到真正需要用到这些数据的时候,再等待异步IO完成. 使用了异步IO,在发起IO请求到实际使用数据这段时间内,程序还可以继续做其他事情).

Android handler异步更新

- - 博客园_首页
private static final int MSG_SUCCESS = 0;// 获取图片成功的标识. private static final int MSG_FAILURE = 1;// 获取图片失败的标识. mImageView.setImageBitmap((Bitmap) msg.obj);// imageview显示从网络获取到的logo.

Android异步接口测试

- - 百度质量部 | 软件测试 | 测试技术 | 百度测试
    基于Android的C/S移动应用中访问后端数据的场景是非常多的,异步接口测试主要是在单元测试完成的基础上检查接口级访问是否正确,主要保证对外请求的组装与发送是否符合后端的约定. 现在项目的异步接口访问都遵循一个特定的访问模式:前台的Activity获取到触发事件后将接受到的参数传给一个异步任务,这些任务大都是AsyncTask的实现——即启动一个新的线程访问后台接口数据,完毕后调用回调函数更新UI展示,示意图如下:.