从事件驱动到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架构的核心仍然是基于消息的发布订阅模式,消息的特定就是准实时,异步和彻底解耦.

事件驱动架构避坑指南

- - DockOne.io
事件驱动架构非常强大,非常适合分布式微服务环境. 通过引入代理中介,事件驱动架构提供了更好的解耦架构、更容易的可扩展性和更高程度的弹性. 请求应答模式 (client-server) vs. 事件流模式 (pub-sub). 但与请求应答客户端-服务器类型架构相比,事件流模式的搭建更复杂. 在Wix过去的几年里,我们逐渐的将我们不断增长的微服务集(目前为 2300 个)从请求-应答模式迁移到事件驱动架构模式.

反向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.