diff --git a/README.md b/README.md index 3a2d1e4..fdf7caf 100644 --- a/README.md +++ b/README.md @@ -141,6 +141,18 @@ var predicate = function(data){ return data.From === "Jack" }; mediator.subscribe("channel", function(data){ alert(data.Message); }, { predicate: predicate }); mediator.publish("channel", { Message: "Hey!", From: "Jack" }); //alerts mediator.publish("channel", { Message: "Hey!", From: "Audrey" }); //doesn't alert + +//You can pass "context" argument as third parameter of if "options" doesn't defined +var listener = { + text: 'Event has been published', + onEvent: function() { + alert(this.text); + } +} +mediator.subscribe("channel", listener.onEvent, listener); +mediator.subscribe("channel", listener.onEvent, {calls: 1}, listener); +mediator.publish("channel"); //we'll have two alerts +mediator.publish("channel"); //we'll have an one alert because the second subscriber does have a "calls:1" option ``` You can remove events by passing in a channel, or a channel and the diff --git a/lib/mediator.js b/lib/mediator.js index 393725d..530f452 100644 --- a/lib/mediator.js +++ b/lib/mediator.js @@ -200,7 +200,8 @@ y = this._subscribers.length, called = false, subscriber, l, - subsBefore,subsAfter; + subsBefore,subsAfter, + predicateResult; // Priority is preserved in the _subscribers index. for(x, y; x < y; x++) { @@ -209,13 +210,27 @@ if(!this.stopped){ subscriber = this._subscribers[x]; if(subscriber.options !== undefined && typeof subscriber.options.predicate === "function"){ - if(subscriber.options.predicate.apply(subscriber.context, data)){ - subscriber.fn.apply(subscriber.context, data); + try { + predicateResult = subscriber.options.predicate.apply(subscriber.context, data); + } catch (ex) { + predicateResult = false; + this._processException(ex); + } + if(predicateResult){ + try { + subscriber.fn.apply(subscriber.context, data); + } catch(ex) { + this._processException(ex); + } called = true; } }else{ subsBefore = this._subscribers.length; - subscriber.fn.apply(subscriber.context, data); + try { + subscriber.fn.apply(subscriber.context, data); + } catch(ex) { + this._processException(ex); + } subsAfter = this._subscribers.length; y = subsAfter; if (subsAfter === subsBefore - 1){ @@ -241,6 +256,22 @@ } this.stopped = false; + }, + + //Throw an exception in another context through setImmediate of setTimeout. + //If neither setImmediate nor setTimeout is defined we log exception's stack to the console (if it is defined) + _processException: function(ex) { + if (typeof setImmediate === 'function') { + setImmediate(function() { + throw ex; + }); + } else if (typeof setTimeout === 'function') { + setTimeout(function() { + throw ex; + }, 0); + } else if (typeof console !== "undefined" && console.log) { + console.log(ex.stack); + } } }; @@ -257,6 +288,15 @@ Mediator.prototype = { + // Returns a true if options contains at least one predefined + // field, such as 'predicate', 'priority', 'calls' + + _isValidOptions: function(options) { + return options.hasOwnProperty('predicate') || + options.hasOwnProperty('priority') || + options.hasOwnProperty('calls'); + }, + // Returns a channel instance based on namespace, for example // application:chat:message:received @@ -288,11 +328,17 @@ // to call the function in to Subscribe. It will create a channel if one // does not exist. Options can include a predicate to determine if it // should be called (based on the data published to it) and a priority - // index. + // index. If options hasn't been defined, context can be passed as third parameter subscribe: function(channelName, fn, options, context){ var channel = this.getChannel(channelName); + //if context has been passed as third argument + if (!context && options && !this._isValidOptions(options)) { + context = options; + options = null; + } + options = options || {}; context = context || {}; diff --git a/test/ChannelSpec.js b/test/ChannelSpec.js index fe35c29..385cd4c 100644 --- a/test/ChannelSpec.js +++ b/test/ChannelSpec.js @@ -299,5 +299,32 @@ describe("Channel", function() { expect(this.a).to.equal("0123"); }); + it("should call subscribers and continue running, if one of subscribers throws an exception", function(){ + var sub1 = function(){ + this.a += "1"; + }, + sub2 = function(){ + this.a += "2"; + throw new Error('error'); + this.a += "444"; + }, + sub3 = function(){ + this.a += "3"; + }, + data = ["data"]; + this.a = "0"; + + channel.addSubscriber(sub1, {}, this); + channel.addSubscriber(sub2, {}, this); + channel.addSubscriber(sub3, {}, this); + try { + channel.publish(data); + this.a += "4"; + } catch(ex) { + this.a += "555"; + } + expect(this.a).to.equal("01234"); + }); + }); }); diff --git a/test/MediatorSpec.js b/test/MediatorSpec.js index 5b57a39..2fa22bd 100644 --- a/test/MediatorSpec.js +++ b/test/MediatorSpec.js @@ -370,5 +370,65 @@ describe("Mediator", function() { mediator.publish("test"); expect(spy).not.called; }); + + it("should call method of context if context has been passed as third argument", function(){ + var context = { + _privateTestMethod: sinon.spy(), + test: function() { + this._privateTestMethod && this._privateTestMethod(); + } + }, + sub; + + sub = mediator.subscribe("test", context.test, null, context); + mediator.publish("test"); + + expect(context._privateTestMethod).called; + }); + + it("should call method of context if context has been passed as second argument and options has been missed", function(){ + var context = { + _privateTestMethod: sinon.spy(), + test: function() { + this._privateTestMethod && this._privateTestMethod(); + } + }, + sub; + + sub = mediator.subscribe("test", context.test, context); + mediator.publish("test"); + + expect(context._privateTestMethod).called; + }); + + it("should call method of context if context has been passed as third argument and options has been passed and defined", function(){ + var context = { + _privateTestMethod: sinon.spy(), + test: function() { + this._privateTestMethod && this._privateTestMethod(); + } + }, + sub; + + sub = mediator.subscribe("test", context.test, {calls: 1}, context); + mediator.publish("test"); + + expect(context._privateTestMethod).called; + }); + + it("should not call method of context if context has been missed", function(){ + var context = { + _privateTestMethod: sinon.spy(), + test: function() { + this._privateTestMethod && this._privateTestMethod(); + } + }, + sub; + + sub = mediator.subscribe("test", context.test, {calls: 1}); + mediator.publish("test"); + + expect(context._privateTestMethod).not.called; + }); }); });