From e909bbad08933f3b4332251926bd7f5c63c1d2a3 Mon Sep 17 00:00:00 2001 From: ifandelse Date: Wed, 1 May 2013 00:42:09 -0400 Subject: [PATCH] Fixed bug reported by @tsgautier in Issue #34. The publish call was incorrectly handling a mutating array of subscribers as it iterated over them. I also fixed the once() call to return the instance. --- example/amd/js/libs/postal/postal.js | 37 +++++-- example/amd/js/libs/postal/postal.min.js | 2 +- example/standard/js/postal.js | 37 +++++-- example/standard/js/postal.min.js | 2 +- lib/postal.js | 37 +++++-- lib/postal.min.js | 2 +- spec/Postal.spec.js | 135 ++++++++++++++--------- src/LocalBus.js | 35 ++++-- src/SubscriptionDefinition.js | 2 + 9 files changed, 192 insertions(+), 97 deletions(-) diff --git a/example/amd/js/libs/postal/postal.js b/example/amd/js/libs/postal/postal.js index c7f0224..e96e762 100755 --- a/example/amd/js/libs/postal/postal.js +++ b/example/amd/js/libs/postal/postal.js @@ -93,6 +93,7 @@ SubscriptionDefinition.prototype = { unsubscribe : function () { + this.inactive = true; postal.configuration.bus.unsubscribe( this ); postal.configuration.bus.publish( { channel : SYSTEM_CHANNEL, @@ -143,6 +144,7 @@ once : function () { this.disposeAfter( 1 ); + return this; }, withConstraint : function ( predicate ) { @@ -242,7 +244,7 @@ } }; var fireSub = function(subDef, envelope) { - if ( postal.configuration.resolver.compare( subDef.topic, envelope.topic ) ) { + if ( !subDef.inactive && postal.configuration.resolver.compare( subDef.topic, envelope.topic ) ) { if ( _.all( subDef.constraints, function ( constraint ) { return constraint.call( subDef.context, envelope.data, envelope ); } ) ) { @@ -253,6 +255,14 @@ } }; + var pubInProgress = false; + var unSubQueue = []; + var clearUnSubQueue = function() { + while(unSubQueue.length) { + unSubQueue.shift().unsubscribe(); + } + }; + var localBus = { addWireTap : function ( callback ) { var self = this; @@ -266,20 +276,22 @@ }, publish : function ( envelope ) { + pubInProgress = true; envelope.timeStamp = new Date(); _.each( this.wireTaps, function ( tap ) { tap( envelope.data, envelope ); } ); if ( this.subscriptions[envelope.channel] ) { - _.each( this.subscriptions[envelope.channel], function ( subscribers ) { - var idx = 0, len = subscribers.length, subDef; - while(idx < len) { - if( subDef = subscribers[idx++] ){ - fireSub(subDef, envelope); - } - } - } ); + _.each( this.subscriptions[envelope.channel], function ( subscribers ) { + var idx = 0, len = subscribers.length, subDef; + while ( idx < len ) { + if ( subDef = subscribers[idx++] ) { + fireSub( subDef, envelope ); + } + } + } ); } + pubInProgress = false; return envelope; }, @@ -314,14 +326,19 @@ wireTaps : [], unsubscribe : function ( config ) { + if(pubInProgress) { + unSubQueue.push(config); + return; + } if ( this.subscriptions[config.channel][config.topic] ) { var len = this.subscriptions[config.channel][config.topic].length, idx = 0; - for ( ; idx < len; idx++ ) { + while(idx < len) { if ( this.subscriptions[config.channel][config.topic][idx] === config ) { this.subscriptions[config.channel][config.topic].splice( idx, 1 ); break; } + idx += 1; } } } diff --git a/example/amd/js/libs/postal/postal.min.js b/example/amd/js/libs/postal/postal.min.js index 3db2218..9b9229b 100755 --- a/example/amd/js/libs/postal/postal.min.js +++ b/example/amd/js/libs/postal/postal.min.js @@ -4,4 +4,4 @@ License: Dual licensed MIT (http://www.opensource.org/licenses/mit-license) & GPL (http://www.opensource.org/licenses/gpl-license) Version 0.8.4 */ -(function(n,t){"object"==typeof module&&module.exports?module.exports=function(n){return n=n||require("underscore"),t(n)}:"function"==typeof define&&define.amd?define(["underscore"],function(i){return t(i,n)}):n.postal=t(n._,n)})(this,function(n){var t="/",i="postal",s=function(){var t;return function(i){var s=!1;return n.isString(i)?(s=i===t,t=i):(s=n.isEqual(i,t),t=n.clone(i)),!s}},c=function(){var t=[];return function(i){var s=!n.any(t,function(t){return n.isObject(i)||n.isArray(i)?n.isEqual(i,t):i===t});return s&&t.push(i),s}},e=function(n){this.channel=n||t};e.prototype.subscribe=function(){return 1===arguments.length?new r(this.channel,arguments[0].topic,arguments[0].callback):new r(this.channel,arguments[0],arguments[1])},e.prototype.publish=function(){var n=1===arguments.length?"[object String]"===Object.prototype.toString.call(arguments[0])?{topic:arguments[0]}:arguments[0]:{topic:arguments[0],data:arguments[1]};return n.channel=this.channel,h.configuration.bus.publish(n)};var r=function(n,t,s){this.channel=n,this.topic=t,this.callback=s,this.constraints=[],this.context=null,h.configuration.bus.publish({channel:i,topic:"subscription.created",data:{event:"subscription.created",channel:n,topic:t}}),h.configuration.bus.subscribe(this)};r.prototype={unsubscribe:function(){h.configuration.bus.unsubscribe(this),h.configuration.bus.publish({channel:i,topic:"subscription.removed",data:{event:"subscription.removed",channel:this.channel,topic:this.topic}})},defer:function(){var n=this.callback;return this.callback=function(t){setTimeout(function(){n(t)},0)},this},disposeAfter:function(t){if(n.isNaN(t)||0>=t)throw"The value provided to disposeAfter (maxCalls) must be a number greater than zero.";var i=this.callback,s=n.after(t,n.bind(function(){this.unsubscribe()},this));return this.callback=function(){i.apply(this.context,arguments),s()},this},distinctUntilChanged:function(){return this.withConstraint(new s),this},distinct:function(){return this.withConstraint(new c),this},once:function(){this.disposeAfter(1)},withConstraint:function(t){if(!n.isFunction(t))throw"Predicate constraint must be a function";return this.constraints.push(t),this},withConstraints:function(t){var i=this;return n.isArray(t)&&n.each(t,function(n){i.withConstraint(n)}),i},withContext:function(n){return this.context=n,this},withDebounce:function(t){if(n.isNaN(t))throw"Milliseconds must be a number";var i=this.callback;return this.callback=n.debounce(i,t),this},withDelay:function(t){if(n.isNaN(t))throw"Milliseconds must be a number";var i=this.callback;return this.callback=function(n){setTimeout(function(){i(n)},t)},this},withThrottle:function(t){if(n.isNaN(t))throw"Milliseconds must be a number";var i=this.callback;return this.callback=n.throttle(i,t),this},subscribe:function(n){return this.callback=n,this}};var o={cache:{},regex:{},compare:function(t,i){var s,c,e,r=this.cache[i]&&this.cache[i][t];return r!==undefined?r:((c=this.regex[t])||(s="^"+n.map(t.split("."),function(n){var t="";return e&&(t="#"!==e?"\\.\\b":"\\b"),t+="#"===n?"[\\s\\S]*":"*"===n?"[^.]+":n,e=n,t}).join("")+"$",c=this.regex[t]=RegExp(s)),this.cache[i]=this.cache[i]||{},this.cache[i][t]=r=c.test(i),r)},reset:function(){this.cache={},this.regex={}}},u=function(t,i){h.configuration.resolver.compare(t.topic,i.topic)&&n.all(t.constraints,function(n){return n.call(t.context,i.data,i)})&&"function"==typeof t.callback&&t.callback.call(t.context,i.data,i)},a={addWireTap:function(n){var t=this;return t.wireTaps.push(n),function(){var i=t.wireTaps.indexOf(n);-1!==i&&t.wireTaps.splice(i,1)}},publish:function(t){return t.timeStamp=new Date,n.each(this.wireTaps,function(n){n(t.data,t)}),this.subscriptions[t.channel]&&n.each(this.subscriptions[t.channel],function(n){for(var i,s=0,c=n.length;c>s;)(i=n[s++])&&u(i,t)}),t},reset:function(){this.subscriptions&&(n.each(this.subscriptions,function(t){n.each(t,function(n){for(;n.length;)n.pop().unsubscribe()})}),this.subscriptions={})},subscribe:function(n){var t,i=this.subscriptions[n.channel];return i||(i=this.subscriptions[n.channel]={}),t=this.subscriptions[n.channel][n.topic],t||(t=this.subscriptions[n.channel][n.topic]=[]),t.push(n),n},subscriptions:{},wireTaps:[],unsubscribe:function(n){if(this.subscriptions[n.channel][n.topic])for(var t=this.subscriptions[n.channel][n.topic].length,i=0;t>i;i++)if(this.subscriptions[n.channel][n.topic][i]===n){this.subscriptions[n.channel][n.topic].splice(i,1);break}}};a.subscriptions[i]={};var h={configuration:{bus:a,resolver:o,DEFAULT_CHANNEL:t,SYSTEM_CHANNEL:i},ChannelDefinition:e,SubscriptionDefinition:r,channel:function(n){return new e(n)},subscribe:function(n){return new r(n.channel||t,n.topic,n.callback)},publish:function(n){return n.channel=n.channel||t,h.configuration.bus.publish(n)},addWireTap:function(n){return this.configuration.bus.addWireTap(n)},linkChannels:function(i,s){var c=[];return i=n.isArray(i)?i:[i],s=n.isArray(s)?s:[s],n.each(i,function(i){i.topic||"#",n.each(s,function(s){var e=s.channel||t;c.push(h.subscribe({channel:i.channel||t,topic:i.topic||"#",callback:function(t,i){var c=n.clone(i);c.topic=n.isFunction(s.topic)?s.topic(i.topic):s.topic||i.topic,c.channel=e,c.data=t,h.publish(c)}}))})}),c},utils:{getSubscribersFor:function(){var n=arguments[0],t=arguments[1];return 1===arguments.length&&(n=arguments[0].channel||h.configuration.DEFAULT_CHANNEL,t=arguments[0].topic),h.configuration.bus.subscriptions[n]&&h.configuration.bus.subscriptions[n].hasOwnProperty(t)?h.configuration.bus.subscriptions[n][t]:[]},reset:function(){h.configuration.bus.reset(),h.configuration.resolver.reset()}}};return h}); \ No newline at end of file +(function(n,t){"object"==typeof module&&module.exports?module.exports=function(n){return n=n||require("underscore"),t(n)}:"function"==typeof define&&define.amd?define(["underscore"],function(i){return t(i,n)}):n.postal=t(n._,n)})(this,function(n){var t="/",i="postal",s=function(){var t;return function(i){var s=!1;return n.isString(i)?(s=i===t,t=i):(s=n.isEqual(i,t),t=n.clone(i)),!s}},e=function(){var t=[];return function(i){var s=!n.any(t,function(t){return n.isObject(i)||n.isArray(i)?n.isEqual(i,t):i===t});return s&&t.push(i),s}},c=function(n){this.channel=n||t};c.prototype.subscribe=function(){return 1===arguments.length?new r(this.channel,arguments[0].topic,arguments[0].callback):new r(this.channel,arguments[0],arguments[1])},c.prototype.publish=function(){var n=1===arguments.length?"[object String]"===Object.prototype.toString.call(arguments[0])?{topic:arguments[0]}:arguments[0]:{topic:arguments[0],data:arguments[1]};return n.channel=this.channel,p.configuration.bus.publish(n)};var r=function(n,t,s){this.channel=n,this.topic=t,this.callback=s,this.constraints=[],this.context=null,p.configuration.bus.publish({channel:i,topic:"subscription.created",data:{event:"subscription.created",channel:n,topic:t}}),p.configuration.bus.subscribe(this)};r.prototype={unsubscribe:function(){this.inactive=!0,p.configuration.bus.unsubscribe(this),p.configuration.bus.publish({channel:i,topic:"subscription.removed",data:{event:"subscription.removed",channel:this.channel,topic:this.topic}})},defer:function(){var n=this.callback;return this.callback=function(t){setTimeout(function(){n(t)},0)},this},disposeAfter:function(t){if(n.isNaN(t)||0>=t)throw"The value provided to disposeAfter (maxCalls) must be a number greater than zero.";var i=this.callback,s=n.after(t,n.bind(function(){this.unsubscribe()},this));return this.callback=function(){i.apply(this.context,arguments),s()},this},distinctUntilChanged:function(){return this.withConstraint(new s),this},distinct:function(){return this.withConstraint(new e),this},once:function(){return this.disposeAfter(1),this},withConstraint:function(t){if(!n.isFunction(t))throw"Predicate constraint must be a function";return this.constraints.push(t),this},withConstraints:function(t){var i=this;return n.isArray(t)&&n.each(t,function(n){i.withConstraint(n)}),i},withContext:function(n){return this.context=n,this},withDebounce:function(t){if(n.isNaN(t))throw"Milliseconds must be a number";var i=this.callback;return this.callback=n.debounce(i,t),this},withDelay:function(t){if(n.isNaN(t))throw"Milliseconds must be a number";var i=this.callback;return this.callback=function(n){setTimeout(function(){i(n)},t)},this},withThrottle:function(t){if(n.isNaN(t))throw"Milliseconds must be a number";var i=this.callback;return this.callback=n.throttle(i,t),this},subscribe:function(n){return this.callback=n,this}};var u={cache:{},regex:{},compare:function(t,i){var s,e,c,r=this.cache[i]&&this.cache[i][t];return r!==undefined?r:((e=this.regex[t])||(s="^"+n.map(t.split("."),function(n){var t="";return c&&(t="#"!==c?"\\.\\b":"\\b"),t+="#"===n?"[\\s\\S]*":"*"===n?"[^.]+":n,c=n,t}).join("")+"$",e=this.regex[t]=RegExp(s)),this.cache[i]=this.cache[i]||{},this.cache[i][t]=r=e.test(i),r)},reset:function(){this.cache={},this.regex={}}},o=function(t,i){!t.inactive&&p.configuration.resolver.compare(t.topic,i.topic)&&n.all(t.constraints,function(n){return n.call(t.context,i.data,i)})&&"function"==typeof t.callback&&t.callback.call(t.context,i.data,i)},a=!1,h=[],l={addWireTap:function(n){var t=this;return t.wireTaps.push(n),function(){var i=t.wireTaps.indexOf(n);-1!==i&&t.wireTaps.splice(i,1)}},publish:function(t){return a=!0,t.timeStamp=new Date,n.each(this.wireTaps,function(n){n(t.data,t)}),this.subscriptions[t.channel]&&n.each(this.subscriptions[t.channel],function(n){for(var i,s=0,e=n.length;e>s;)(i=n[s++])&&o(i,t)}),a=!1,t},reset:function(){this.subscriptions&&(n.each(this.subscriptions,function(t){n.each(t,function(n){for(;n.length;)n.pop().unsubscribe()})}),this.subscriptions={})},subscribe:function(n){var t,i=this.subscriptions[n.channel];return i||(i=this.subscriptions[n.channel]={}),t=this.subscriptions[n.channel][n.topic],t||(t=this.subscriptions[n.channel][n.topic]=[]),t.push(n),n},subscriptions:{},wireTaps:[],unsubscribe:function(n){if(a)return h.push(n),undefined;if(this.subscriptions[n.channel][n.topic])for(var t=this.subscriptions[n.channel][n.topic].length,i=0;t>i;){if(this.subscriptions[n.channel][n.topic][i]===n){this.subscriptions[n.channel][n.topic].splice(i,1);break}i+=1}}};l.subscriptions[i]={};var p={configuration:{bus:l,resolver:u,DEFAULT_CHANNEL:t,SYSTEM_CHANNEL:i},ChannelDefinition:c,SubscriptionDefinition:r,channel:function(n){return new c(n)},subscribe:function(n){return new r(n.channel||t,n.topic,n.callback)},publish:function(n){return n.channel=n.channel||t,p.configuration.bus.publish(n)},addWireTap:function(n){return this.configuration.bus.addWireTap(n)},linkChannels:function(i,s){var e=[];return i=n.isArray(i)?i:[i],s=n.isArray(s)?s:[s],n.each(i,function(i){i.topic||"#",n.each(s,function(s){var c=s.channel||t;e.push(p.subscribe({channel:i.channel||t,topic:i.topic||"#",callback:function(t,i){var e=n.clone(i);e.topic=n.isFunction(s.topic)?s.topic(i.topic):s.topic||i.topic,e.channel=c,e.data=t,p.publish(e)}}))})}),e},utils:{getSubscribersFor:function(){var n=arguments[0],t=arguments[1];return 1===arguments.length&&(n=arguments[0].channel||p.configuration.DEFAULT_CHANNEL,t=arguments[0].topic),p.configuration.bus.subscriptions[n]&&p.configuration.bus.subscriptions[n].hasOwnProperty(t)?p.configuration.bus.subscriptions[n][t]:[]},reset:function(){p.configuration.bus.reset(),p.configuration.resolver.reset()}}};return p}); \ No newline at end of file diff --git a/example/standard/js/postal.js b/example/standard/js/postal.js index c7f0224..e96e762 100755 --- a/example/standard/js/postal.js +++ b/example/standard/js/postal.js @@ -93,6 +93,7 @@ SubscriptionDefinition.prototype = { unsubscribe : function () { + this.inactive = true; postal.configuration.bus.unsubscribe( this ); postal.configuration.bus.publish( { channel : SYSTEM_CHANNEL, @@ -143,6 +144,7 @@ once : function () { this.disposeAfter( 1 ); + return this; }, withConstraint : function ( predicate ) { @@ -242,7 +244,7 @@ } }; var fireSub = function(subDef, envelope) { - if ( postal.configuration.resolver.compare( subDef.topic, envelope.topic ) ) { + if ( !subDef.inactive && postal.configuration.resolver.compare( subDef.topic, envelope.topic ) ) { if ( _.all( subDef.constraints, function ( constraint ) { return constraint.call( subDef.context, envelope.data, envelope ); } ) ) { @@ -253,6 +255,14 @@ } }; + var pubInProgress = false; + var unSubQueue = []; + var clearUnSubQueue = function() { + while(unSubQueue.length) { + unSubQueue.shift().unsubscribe(); + } + }; + var localBus = { addWireTap : function ( callback ) { var self = this; @@ -266,20 +276,22 @@ }, publish : function ( envelope ) { + pubInProgress = true; envelope.timeStamp = new Date(); _.each( this.wireTaps, function ( tap ) { tap( envelope.data, envelope ); } ); if ( this.subscriptions[envelope.channel] ) { - _.each( this.subscriptions[envelope.channel], function ( subscribers ) { - var idx = 0, len = subscribers.length, subDef; - while(idx < len) { - if( subDef = subscribers[idx++] ){ - fireSub(subDef, envelope); - } - } - } ); + _.each( this.subscriptions[envelope.channel], function ( subscribers ) { + var idx = 0, len = subscribers.length, subDef; + while ( idx < len ) { + if ( subDef = subscribers[idx++] ) { + fireSub( subDef, envelope ); + } + } + } ); } + pubInProgress = false; return envelope; }, @@ -314,14 +326,19 @@ wireTaps : [], unsubscribe : function ( config ) { + if(pubInProgress) { + unSubQueue.push(config); + return; + } if ( this.subscriptions[config.channel][config.topic] ) { var len = this.subscriptions[config.channel][config.topic].length, idx = 0; - for ( ; idx < len; idx++ ) { + while(idx < len) { if ( this.subscriptions[config.channel][config.topic][idx] === config ) { this.subscriptions[config.channel][config.topic].splice( idx, 1 ); break; } + idx += 1; } } } diff --git a/example/standard/js/postal.min.js b/example/standard/js/postal.min.js index 3db2218..9b9229b 100755 --- a/example/standard/js/postal.min.js +++ b/example/standard/js/postal.min.js @@ -4,4 +4,4 @@ License: Dual licensed MIT (http://www.opensource.org/licenses/mit-license) & GPL (http://www.opensource.org/licenses/gpl-license) Version 0.8.4 */ -(function(n,t){"object"==typeof module&&module.exports?module.exports=function(n){return n=n||require("underscore"),t(n)}:"function"==typeof define&&define.amd?define(["underscore"],function(i){return t(i,n)}):n.postal=t(n._,n)})(this,function(n){var t="/",i="postal",s=function(){var t;return function(i){var s=!1;return n.isString(i)?(s=i===t,t=i):(s=n.isEqual(i,t),t=n.clone(i)),!s}},c=function(){var t=[];return function(i){var s=!n.any(t,function(t){return n.isObject(i)||n.isArray(i)?n.isEqual(i,t):i===t});return s&&t.push(i),s}},e=function(n){this.channel=n||t};e.prototype.subscribe=function(){return 1===arguments.length?new r(this.channel,arguments[0].topic,arguments[0].callback):new r(this.channel,arguments[0],arguments[1])},e.prototype.publish=function(){var n=1===arguments.length?"[object String]"===Object.prototype.toString.call(arguments[0])?{topic:arguments[0]}:arguments[0]:{topic:arguments[0],data:arguments[1]};return n.channel=this.channel,h.configuration.bus.publish(n)};var r=function(n,t,s){this.channel=n,this.topic=t,this.callback=s,this.constraints=[],this.context=null,h.configuration.bus.publish({channel:i,topic:"subscription.created",data:{event:"subscription.created",channel:n,topic:t}}),h.configuration.bus.subscribe(this)};r.prototype={unsubscribe:function(){h.configuration.bus.unsubscribe(this),h.configuration.bus.publish({channel:i,topic:"subscription.removed",data:{event:"subscription.removed",channel:this.channel,topic:this.topic}})},defer:function(){var n=this.callback;return this.callback=function(t){setTimeout(function(){n(t)},0)},this},disposeAfter:function(t){if(n.isNaN(t)||0>=t)throw"The value provided to disposeAfter (maxCalls) must be a number greater than zero.";var i=this.callback,s=n.after(t,n.bind(function(){this.unsubscribe()},this));return this.callback=function(){i.apply(this.context,arguments),s()},this},distinctUntilChanged:function(){return this.withConstraint(new s),this},distinct:function(){return this.withConstraint(new c),this},once:function(){this.disposeAfter(1)},withConstraint:function(t){if(!n.isFunction(t))throw"Predicate constraint must be a function";return this.constraints.push(t),this},withConstraints:function(t){var i=this;return n.isArray(t)&&n.each(t,function(n){i.withConstraint(n)}),i},withContext:function(n){return this.context=n,this},withDebounce:function(t){if(n.isNaN(t))throw"Milliseconds must be a number";var i=this.callback;return this.callback=n.debounce(i,t),this},withDelay:function(t){if(n.isNaN(t))throw"Milliseconds must be a number";var i=this.callback;return this.callback=function(n){setTimeout(function(){i(n)},t)},this},withThrottle:function(t){if(n.isNaN(t))throw"Milliseconds must be a number";var i=this.callback;return this.callback=n.throttle(i,t),this},subscribe:function(n){return this.callback=n,this}};var o={cache:{},regex:{},compare:function(t,i){var s,c,e,r=this.cache[i]&&this.cache[i][t];return r!==undefined?r:((c=this.regex[t])||(s="^"+n.map(t.split("."),function(n){var t="";return e&&(t="#"!==e?"\\.\\b":"\\b"),t+="#"===n?"[\\s\\S]*":"*"===n?"[^.]+":n,e=n,t}).join("")+"$",c=this.regex[t]=RegExp(s)),this.cache[i]=this.cache[i]||{},this.cache[i][t]=r=c.test(i),r)},reset:function(){this.cache={},this.regex={}}},u=function(t,i){h.configuration.resolver.compare(t.topic,i.topic)&&n.all(t.constraints,function(n){return n.call(t.context,i.data,i)})&&"function"==typeof t.callback&&t.callback.call(t.context,i.data,i)},a={addWireTap:function(n){var t=this;return t.wireTaps.push(n),function(){var i=t.wireTaps.indexOf(n);-1!==i&&t.wireTaps.splice(i,1)}},publish:function(t){return t.timeStamp=new Date,n.each(this.wireTaps,function(n){n(t.data,t)}),this.subscriptions[t.channel]&&n.each(this.subscriptions[t.channel],function(n){for(var i,s=0,c=n.length;c>s;)(i=n[s++])&&u(i,t)}),t},reset:function(){this.subscriptions&&(n.each(this.subscriptions,function(t){n.each(t,function(n){for(;n.length;)n.pop().unsubscribe()})}),this.subscriptions={})},subscribe:function(n){var t,i=this.subscriptions[n.channel];return i||(i=this.subscriptions[n.channel]={}),t=this.subscriptions[n.channel][n.topic],t||(t=this.subscriptions[n.channel][n.topic]=[]),t.push(n),n},subscriptions:{},wireTaps:[],unsubscribe:function(n){if(this.subscriptions[n.channel][n.topic])for(var t=this.subscriptions[n.channel][n.topic].length,i=0;t>i;i++)if(this.subscriptions[n.channel][n.topic][i]===n){this.subscriptions[n.channel][n.topic].splice(i,1);break}}};a.subscriptions[i]={};var h={configuration:{bus:a,resolver:o,DEFAULT_CHANNEL:t,SYSTEM_CHANNEL:i},ChannelDefinition:e,SubscriptionDefinition:r,channel:function(n){return new e(n)},subscribe:function(n){return new r(n.channel||t,n.topic,n.callback)},publish:function(n){return n.channel=n.channel||t,h.configuration.bus.publish(n)},addWireTap:function(n){return this.configuration.bus.addWireTap(n)},linkChannels:function(i,s){var c=[];return i=n.isArray(i)?i:[i],s=n.isArray(s)?s:[s],n.each(i,function(i){i.topic||"#",n.each(s,function(s){var e=s.channel||t;c.push(h.subscribe({channel:i.channel||t,topic:i.topic||"#",callback:function(t,i){var c=n.clone(i);c.topic=n.isFunction(s.topic)?s.topic(i.topic):s.topic||i.topic,c.channel=e,c.data=t,h.publish(c)}}))})}),c},utils:{getSubscribersFor:function(){var n=arguments[0],t=arguments[1];return 1===arguments.length&&(n=arguments[0].channel||h.configuration.DEFAULT_CHANNEL,t=arguments[0].topic),h.configuration.bus.subscriptions[n]&&h.configuration.bus.subscriptions[n].hasOwnProperty(t)?h.configuration.bus.subscriptions[n][t]:[]},reset:function(){h.configuration.bus.reset(),h.configuration.resolver.reset()}}};return h}); \ No newline at end of file +(function(n,t){"object"==typeof module&&module.exports?module.exports=function(n){return n=n||require("underscore"),t(n)}:"function"==typeof define&&define.amd?define(["underscore"],function(i){return t(i,n)}):n.postal=t(n._,n)})(this,function(n){var t="/",i="postal",s=function(){var t;return function(i){var s=!1;return n.isString(i)?(s=i===t,t=i):(s=n.isEqual(i,t),t=n.clone(i)),!s}},e=function(){var t=[];return function(i){var s=!n.any(t,function(t){return n.isObject(i)||n.isArray(i)?n.isEqual(i,t):i===t});return s&&t.push(i),s}},c=function(n){this.channel=n||t};c.prototype.subscribe=function(){return 1===arguments.length?new r(this.channel,arguments[0].topic,arguments[0].callback):new r(this.channel,arguments[0],arguments[1])},c.prototype.publish=function(){var n=1===arguments.length?"[object String]"===Object.prototype.toString.call(arguments[0])?{topic:arguments[0]}:arguments[0]:{topic:arguments[0],data:arguments[1]};return n.channel=this.channel,p.configuration.bus.publish(n)};var r=function(n,t,s){this.channel=n,this.topic=t,this.callback=s,this.constraints=[],this.context=null,p.configuration.bus.publish({channel:i,topic:"subscription.created",data:{event:"subscription.created",channel:n,topic:t}}),p.configuration.bus.subscribe(this)};r.prototype={unsubscribe:function(){this.inactive=!0,p.configuration.bus.unsubscribe(this),p.configuration.bus.publish({channel:i,topic:"subscription.removed",data:{event:"subscription.removed",channel:this.channel,topic:this.topic}})},defer:function(){var n=this.callback;return this.callback=function(t){setTimeout(function(){n(t)},0)},this},disposeAfter:function(t){if(n.isNaN(t)||0>=t)throw"The value provided to disposeAfter (maxCalls) must be a number greater than zero.";var i=this.callback,s=n.after(t,n.bind(function(){this.unsubscribe()},this));return this.callback=function(){i.apply(this.context,arguments),s()},this},distinctUntilChanged:function(){return this.withConstraint(new s),this},distinct:function(){return this.withConstraint(new e),this},once:function(){return this.disposeAfter(1),this},withConstraint:function(t){if(!n.isFunction(t))throw"Predicate constraint must be a function";return this.constraints.push(t),this},withConstraints:function(t){var i=this;return n.isArray(t)&&n.each(t,function(n){i.withConstraint(n)}),i},withContext:function(n){return this.context=n,this},withDebounce:function(t){if(n.isNaN(t))throw"Milliseconds must be a number";var i=this.callback;return this.callback=n.debounce(i,t),this},withDelay:function(t){if(n.isNaN(t))throw"Milliseconds must be a number";var i=this.callback;return this.callback=function(n){setTimeout(function(){i(n)},t)},this},withThrottle:function(t){if(n.isNaN(t))throw"Milliseconds must be a number";var i=this.callback;return this.callback=n.throttle(i,t),this},subscribe:function(n){return this.callback=n,this}};var u={cache:{},regex:{},compare:function(t,i){var s,e,c,r=this.cache[i]&&this.cache[i][t];return r!==undefined?r:((e=this.regex[t])||(s="^"+n.map(t.split("."),function(n){var t="";return c&&(t="#"!==c?"\\.\\b":"\\b"),t+="#"===n?"[\\s\\S]*":"*"===n?"[^.]+":n,c=n,t}).join("")+"$",e=this.regex[t]=RegExp(s)),this.cache[i]=this.cache[i]||{},this.cache[i][t]=r=e.test(i),r)},reset:function(){this.cache={},this.regex={}}},o=function(t,i){!t.inactive&&p.configuration.resolver.compare(t.topic,i.topic)&&n.all(t.constraints,function(n){return n.call(t.context,i.data,i)})&&"function"==typeof t.callback&&t.callback.call(t.context,i.data,i)},a=!1,h=[],l={addWireTap:function(n){var t=this;return t.wireTaps.push(n),function(){var i=t.wireTaps.indexOf(n);-1!==i&&t.wireTaps.splice(i,1)}},publish:function(t){return a=!0,t.timeStamp=new Date,n.each(this.wireTaps,function(n){n(t.data,t)}),this.subscriptions[t.channel]&&n.each(this.subscriptions[t.channel],function(n){for(var i,s=0,e=n.length;e>s;)(i=n[s++])&&o(i,t)}),a=!1,t},reset:function(){this.subscriptions&&(n.each(this.subscriptions,function(t){n.each(t,function(n){for(;n.length;)n.pop().unsubscribe()})}),this.subscriptions={})},subscribe:function(n){var t,i=this.subscriptions[n.channel];return i||(i=this.subscriptions[n.channel]={}),t=this.subscriptions[n.channel][n.topic],t||(t=this.subscriptions[n.channel][n.topic]=[]),t.push(n),n},subscriptions:{},wireTaps:[],unsubscribe:function(n){if(a)return h.push(n),undefined;if(this.subscriptions[n.channel][n.topic])for(var t=this.subscriptions[n.channel][n.topic].length,i=0;t>i;){if(this.subscriptions[n.channel][n.topic][i]===n){this.subscriptions[n.channel][n.topic].splice(i,1);break}i+=1}}};l.subscriptions[i]={};var p={configuration:{bus:l,resolver:u,DEFAULT_CHANNEL:t,SYSTEM_CHANNEL:i},ChannelDefinition:c,SubscriptionDefinition:r,channel:function(n){return new c(n)},subscribe:function(n){return new r(n.channel||t,n.topic,n.callback)},publish:function(n){return n.channel=n.channel||t,p.configuration.bus.publish(n)},addWireTap:function(n){return this.configuration.bus.addWireTap(n)},linkChannels:function(i,s){var e=[];return i=n.isArray(i)?i:[i],s=n.isArray(s)?s:[s],n.each(i,function(i){i.topic||"#",n.each(s,function(s){var c=s.channel||t;e.push(p.subscribe({channel:i.channel||t,topic:i.topic||"#",callback:function(t,i){var e=n.clone(i);e.topic=n.isFunction(s.topic)?s.topic(i.topic):s.topic||i.topic,e.channel=c,e.data=t,p.publish(e)}}))})}),e},utils:{getSubscribersFor:function(){var n=arguments[0],t=arguments[1];return 1===arguments.length&&(n=arguments[0].channel||p.configuration.DEFAULT_CHANNEL,t=arguments[0].topic),p.configuration.bus.subscriptions[n]&&p.configuration.bus.subscriptions[n].hasOwnProperty(t)?p.configuration.bus.subscriptions[n][t]:[]},reset:function(){p.configuration.bus.reset(),p.configuration.resolver.reset()}}};return p}); \ No newline at end of file diff --git a/lib/postal.js b/lib/postal.js index c7f0224..e96e762 100755 --- a/lib/postal.js +++ b/lib/postal.js @@ -93,6 +93,7 @@ SubscriptionDefinition.prototype = { unsubscribe : function () { + this.inactive = true; postal.configuration.bus.unsubscribe( this ); postal.configuration.bus.publish( { channel : SYSTEM_CHANNEL, @@ -143,6 +144,7 @@ once : function () { this.disposeAfter( 1 ); + return this; }, withConstraint : function ( predicate ) { @@ -242,7 +244,7 @@ } }; var fireSub = function(subDef, envelope) { - if ( postal.configuration.resolver.compare( subDef.topic, envelope.topic ) ) { + if ( !subDef.inactive && postal.configuration.resolver.compare( subDef.topic, envelope.topic ) ) { if ( _.all( subDef.constraints, function ( constraint ) { return constraint.call( subDef.context, envelope.data, envelope ); } ) ) { @@ -253,6 +255,14 @@ } }; + var pubInProgress = false; + var unSubQueue = []; + var clearUnSubQueue = function() { + while(unSubQueue.length) { + unSubQueue.shift().unsubscribe(); + } + }; + var localBus = { addWireTap : function ( callback ) { var self = this; @@ -266,20 +276,22 @@ }, publish : function ( envelope ) { + pubInProgress = true; envelope.timeStamp = new Date(); _.each( this.wireTaps, function ( tap ) { tap( envelope.data, envelope ); } ); if ( this.subscriptions[envelope.channel] ) { - _.each( this.subscriptions[envelope.channel], function ( subscribers ) { - var idx = 0, len = subscribers.length, subDef; - while(idx < len) { - if( subDef = subscribers[idx++] ){ - fireSub(subDef, envelope); - } - } - } ); + _.each( this.subscriptions[envelope.channel], function ( subscribers ) { + var idx = 0, len = subscribers.length, subDef; + while ( idx < len ) { + if ( subDef = subscribers[idx++] ) { + fireSub( subDef, envelope ); + } + } + } ); } + pubInProgress = false; return envelope; }, @@ -314,14 +326,19 @@ wireTaps : [], unsubscribe : function ( config ) { + if(pubInProgress) { + unSubQueue.push(config); + return; + } if ( this.subscriptions[config.channel][config.topic] ) { var len = this.subscriptions[config.channel][config.topic].length, idx = 0; - for ( ; idx < len; idx++ ) { + while(idx < len) { if ( this.subscriptions[config.channel][config.topic][idx] === config ) { this.subscriptions[config.channel][config.topic].splice( idx, 1 ); break; } + idx += 1; } } } diff --git a/lib/postal.min.js b/lib/postal.min.js index 3db2218..9b9229b 100755 --- a/lib/postal.min.js +++ b/lib/postal.min.js @@ -4,4 +4,4 @@ License: Dual licensed MIT (http://www.opensource.org/licenses/mit-license) & GPL (http://www.opensource.org/licenses/gpl-license) Version 0.8.4 */ -(function(n,t){"object"==typeof module&&module.exports?module.exports=function(n){return n=n||require("underscore"),t(n)}:"function"==typeof define&&define.amd?define(["underscore"],function(i){return t(i,n)}):n.postal=t(n._,n)})(this,function(n){var t="/",i="postal",s=function(){var t;return function(i){var s=!1;return n.isString(i)?(s=i===t,t=i):(s=n.isEqual(i,t),t=n.clone(i)),!s}},c=function(){var t=[];return function(i){var s=!n.any(t,function(t){return n.isObject(i)||n.isArray(i)?n.isEqual(i,t):i===t});return s&&t.push(i),s}},e=function(n){this.channel=n||t};e.prototype.subscribe=function(){return 1===arguments.length?new r(this.channel,arguments[0].topic,arguments[0].callback):new r(this.channel,arguments[0],arguments[1])},e.prototype.publish=function(){var n=1===arguments.length?"[object String]"===Object.prototype.toString.call(arguments[0])?{topic:arguments[0]}:arguments[0]:{topic:arguments[0],data:arguments[1]};return n.channel=this.channel,h.configuration.bus.publish(n)};var r=function(n,t,s){this.channel=n,this.topic=t,this.callback=s,this.constraints=[],this.context=null,h.configuration.bus.publish({channel:i,topic:"subscription.created",data:{event:"subscription.created",channel:n,topic:t}}),h.configuration.bus.subscribe(this)};r.prototype={unsubscribe:function(){h.configuration.bus.unsubscribe(this),h.configuration.bus.publish({channel:i,topic:"subscription.removed",data:{event:"subscription.removed",channel:this.channel,topic:this.topic}})},defer:function(){var n=this.callback;return this.callback=function(t){setTimeout(function(){n(t)},0)},this},disposeAfter:function(t){if(n.isNaN(t)||0>=t)throw"The value provided to disposeAfter (maxCalls) must be a number greater than zero.";var i=this.callback,s=n.after(t,n.bind(function(){this.unsubscribe()},this));return this.callback=function(){i.apply(this.context,arguments),s()},this},distinctUntilChanged:function(){return this.withConstraint(new s),this},distinct:function(){return this.withConstraint(new c),this},once:function(){this.disposeAfter(1)},withConstraint:function(t){if(!n.isFunction(t))throw"Predicate constraint must be a function";return this.constraints.push(t),this},withConstraints:function(t){var i=this;return n.isArray(t)&&n.each(t,function(n){i.withConstraint(n)}),i},withContext:function(n){return this.context=n,this},withDebounce:function(t){if(n.isNaN(t))throw"Milliseconds must be a number";var i=this.callback;return this.callback=n.debounce(i,t),this},withDelay:function(t){if(n.isNaN(t))throw"Milliseconds must be a number";var i=this.callback;return this.callback=function(n){setTimeout(function(){i(n)},t)},this},withThrottle:function(t){if(n.isNaN(t))throw"Milliseconds must be a number";var i=this.callback;return this.callback=n.throttle(i,t),this},subscribe:function(n){return this.callback=n,this}};var o={cache:{},regex:{},compare:function(t,i){var s,c,e,r=this.cache[i]&&this.cache[i][t];return r!==undefined?r:((c=this.regex[t])||(s="^"+n.map(t.split("."),function(n){var t="";return e&&(t="#"!==e?"\\.\\b":"\\b"),t+="#"===n?"[\\s\\S]*":"*"===n?"[^.]+":n,e=n,t}).join("")+"$",c=this.regex[t]=RegExp(s)),this.cache[i]=this.cache[i]||{},this.cache[i][t]=r=c.test(i),r)},reset:function(){this.cache={},this.regex={}}},u=function(t,i){h.configuration.resolver.compare(t.topic,i.topic)&&n.all(t.constraints,function(n){return n.call(t.context,i.data,i)})&&"function"==typeof t.callback&&t.callback.call(t.context,i.data,i)},a={addWireTap:function(n){var t=this;return t.wireTaps.push(n),function(){var i=t.wireTaps.indexOf(n);-1!==i&&t.wireTaps.splice(i,1)}},publish:function(t){return t.timeStamp=new Date,n.each(this.wireTaps,function(n){n(t.data,t)}),this.subscriptions[t.channel]&&n.each(this.subscriptions[t.channel],function(n){for(var i,s=0,c=n.length;c>s;)(i=n[s++])&&u(i,t)}),t},reset:function(){this.subscriptions&&(n.each(this.subscriptions,function(t){n.each(t,function(n){for(;n.length;)n.pop().unsubscribe()})}),this.subscriptions={})},subscribe:function(n){var t,i=this.subscriptions[n.channel];return i||(i=this.subscriptions[n.channel]={}),t=this.subscriptions[n.channel][n.topic],t||(t=this.subscriptions[n.channel][n.topic]=[]),t.push(n),n},subscriptions:{},wireTaps:[],unsubscribe:function(n){if(this.subscriptions[n.channel][n.topic])for(var t=this.subscriptions[n.channel][n.topic].length,i=0;t>i;i++)if(this.subscriptions[n.channel][n.topic][i]===n){this.subscriptions[n.channel][n.topic].splice(i,1);break}}};a.subscriptions[i]={};var h={configuration:{bus:a,resolver:o,DEFAULT_CHANNEL:t,SYSTEM_CHANNEL:i},ChannelDefinition:e,SubscriptionDefinition:r,channel:function(n){return new e(n)},subscribe:function(n){return new r(n.channel||t,n.topic,n.callback)},publish:function(n){return n.channel=n.channel||t,h.configuration.bus.publish(n)},addWireTap:function(n){return this.configuration.bus.addWireTap(n)},linkChannels:function(i,s){var c=[];return i=n.isArray(i)?i:[i],s=n.isArray(s)?s:[s],n.each(i,function(i){i.topic||"#",n.each(s,function(s){var e=s.channel||t;c.push(h.subscribe({channel:i.channel||t,topic:i.topic||"#",callback:function(t,i){var c=n.clone(i);c.topic=n.isFunction(s.topic)?s.topic(i.topic):s.topic||i.topic,c.channel=e,c.data=t,h.publish(c)}}))})}),c},utils:{getSubscribersFor:function(){var n=arguments[0],t=arguments[1];return 1===arguments.length&&(n=arguments[0].channel||h.configuration.DEFAULT_CHANNEL,t=arguments[0].topic),h.configuration.bus.subscriptions[n]&&h.configuration.bus.subscriptions[n].hasOwnProperty(t)?h.configuration.bus.subscriptions[n][t]:[]},reset:function(){h.configuration.bus.reset(),h.configuration.resolver.reset()}}};return h}); \ No newline at end of file +(function(n,t){"object"==typeof module&&module.exports?module.exports=function(n){return n=n||require("underscore"),t(n)}:"function"==typeof define&&define.amd?define(["underscore"],function(i){return t(i,n)}):n.postal=t(n._,n)})(this,function(n){var t="/",i="postal",s=function(){var t;return function(i){var s=!1;return n.isString(i)?(s=i===t,t=i):(s=n.isEqual(i,t),t=n.clone(i)),!s}},e=function(){var t=[];return function(i){var s=!n.any(t,function(t){return n.isObject(i)||n.isArray(i)?n.isEqual(i,t):i===t});return s&&t.push(i),s}},c=function(n){this.channel=n||t};c.prototype.subscribe=function(){return 1===arguments.length?new r(this.channel,arguments[0].topic,arguments[0].callback):new r(this.channel,arguments[0],arguments[1])},c.prototype.publish=function(){var n=1===arguments.length?"[object String]"===Object.prototype.toString.call(arguments[0])?{topic:arguments[0]}:arguments[0]:{topic:arguments[0],data:arguments[1]};return n.channel=this.channel,p.configuration.bus.publish(n)};var r=function(n,t,s){this.channel=n,this.topic=t,this.callback=s,this.constraints=[],this.context=null,p.configuration.bus.publish({channel:i,topic:"subscription.created",data:{event:"subscription.created",channel:n,topic:t}}),p.configuration.bus.subscribe(this)};r.prototype={unsubscribe:function(){this.inactive=!0,p.configuration.bus.unsubscribe(this),p.configuration.bus.publish({channel:i,topic:"subscription.removed",data:{event:"subscription.removed",channel:this.channel,topic:this.topic}})},defer:function(){var n=this.callback;return this.callback=function(t){setTimeout(function(){n(t)},0)},this},disposeAfter:function(t){if(n.isNaN(t)||0>=t)throw"The value provided to disposeAfter (maxCalls) must be a number greater than zero.";var i=this.callback,s=n.after(t,n.bind(function(){this.unsubscribe()},this));return this.callback=function(){i.apply(this.context,arguments),s()},this},distinctUntilChanged:function(){return this.withConstraint(new s),this},distinct:function(){return this.withConstraint(new e),this},once:function(){return this.disposeAfter(1),this},withConstraint:function(t){if(!n.isFunction(t))throw"Predicate constraint must be a function";return this.constraints.push(t),this},withConstraints:function(t){var i=this;return n.isArray(t)&&n.each(t,function(n){i.withConstraint(n)}),i},withContext:function(n){return this.context=n,this},withDebounce:function(t){if(n.isNaN(t))throw"Milliseconds must be a number";var i=this.callback;return this.callback=n.debounce(i,t),this},withDelay:function(t){if(n.isNaN(t))throw"Milliseconds must be a number";var i=this.callback;return this.callback=function(n){setTimeout(function(){i(n)},t)},this},withThrottle:function(t){if(n.isNaN(t))throw"Milliseconds must be a number";var i=this.callback;return this.callback=n.throttle(i,t),this},subscribe:function(n){return this.callback=n,this}};var u={cache:{},regex:{},compare:function(t,i){var s,e,c,r=this.cache[i]&&this.cache[i][t];return r!==undefined?r:((e=this.regex[t])||(s="^"+n.map(t.split("."),function(n){var t="";return c&&(t="#"!==c?"\\.\\b":"\\b"),t+="#"===n?"[\\s\\S]*":"*"===n?"[^.]+":n,c=n,t}).join("")+"$",e=this.regex[t]=RegExp(s)),this.cache[i]=this.cache[i]||{},this.cache[i][t]=r=e.test(i),r)},reset:function(){this.cache={},this.regex={}}},o=function(t,i){!t.inactive&&p.configuration.resolver.compare(t.topic,i.topic)&&n.all(t.constraints,function(n){return n.call(t.context,i.data,i)})&&"function"==typeof t.callback&&t.callback.call(t.context,i.data,i)},a=!1,h=[],l={addWireTap:function(n){var t=this;return t.wireTaps.push(n),function(){var i=t.wireTaps.indexOf(n);-1!==i&&t.wireTaps.splice(i,1)}},publish:function(t){return a=!0,t.timeStamp=new Date,n.each(this.wireTaps,function(n){n(t.data,t)}),this.subscriptions[t.channel]&&n.each(this.subscriptions[t.channel],function(n){for(var i,s=0,e=n.length;e>s;)(i=n[s++])&&o(i,t)}),a=!1,t},reset:function(){this.subscriptions&&(n.each(this.subscriptions,function(t){n.each(t,function(n){for(;n.length;)n.pop().unsubscribe()})}),this.subscriptions={})},subscribe:function(n){var t,i=this.subscriptions[n.channel];return i||(i=this.subscriptions[n.channel]={}),t=this.subscriptions[n.channel][n.topic],t||(t=this.subscriptions[n.channel][n.topic]=[]),t.push(n),n},subscriptions:{},wireTaps:[],unsubscribe:function(n){if(a)return h.push(n),undefined;if(this.subscriptions[n.channel][n.topic])for(var t=this.subscriptions[n.channel][n.topic].length,i=0;t>i;){if(this.subscriptions[n.channel][n.topic][i]===n){this.subscriptions[n.channel][n.topic].splice(i,1);break}i+=1}}};l.subscriptions[i]={};var p={configuration:{bus:l,resolver:u,DEFAULT_CHANNEL:t,SYSTEM_CHANNEL:i},ChannelDefinition:c,SubscriptionDefinition:r,channel:function(n){return new c(n)},subscribe:function(n){return new r(n.channel||t,n.topic,n.callback)},publish:function(n){return n.channel=n.channel||t,p.configuration.bus.publish(n)},addWireTap:function(n){return this.configuration.bus.addWireTap(n)},linkChannels:function(i,s){var e=[];return i=n.isArray(i)?i:[i],s=n.isArray(s)?s:[s],n.each(i,function(i){i.topic||"#",n.each(s,function(s){var c=s.channel||t;e.push(p.subscribe({channel:i.channel||t,topic:i.topic||"#",callback:function(t,i){var e=n.clone(i);e.topic=n.isFunction(s.topic)?s.topic(i.topic):s.topic||i.topic,e.channel=c,e.data=t,p.publish(e)}}))})}),e},utils:{getSubscribersFor:function(){var n=arguments[0],t=arguments[1];return 1===arguments.length&&(n=arguments[0].channel||p.configuration.DEFAULT_CHANNEL,t=arguments[0].topic),p.configuration.bus.subscriptions[n]&&p.configuration.bus.subscriptions[n].hasOwnProperty(t)?p.configuration.bus.subscriptions[n][t]:[]},reset:function(){p.configuration.bus.reset(),p.configuration.resolver.reset()}}};return p}); \ No newline at end of file diff --git a/spec/Postal.spec.js b/spec/Postal.spec.js index 62bbf15..e02154f 100644 --- a/spec/Postal.spec.js +++ b/spec/Postal.spec.js @@ -50,41 +50,68 @@ describe( "Postal", function () { } ); } ); describe( "When unsubscribing", function () { - var subExistsBefore = false, - subExistsAfter = true; - var systemSubscription = {}; - before( function () { - systemSubscription = postal.subscribe( { - channel : "postal", - topic : "subscription.*", - callback : function ( data, env ) { - if ( data.event && - data.event == "subscription.removed" && - data.channel == "MyChannel" && - data.topic == "MyTopic" ) { - caughtUnsubscribeEvent = true; + describe( "With a single subscription", function(){ + var subExistsBefore = false, + subExistsAfter = true; + var systemSubscription = {}; + before( function () { + systemSubscription = postal.subscribe( { + channel : "postal", + topic : "subscription.*", + callback : function ( data, env ) { + if ( data.event && + data.event == "subscription.removed" && + data.channel == "MyChannel" && + data.topic == "MyTopic" ) { + caughtUnsubscribeEvent = true; + } } - ; - } + } ); + subscription = postal.channel( "MyChannel" ).subscribe( "MyTopic" , function () {} ); + subExistsBefore = postal.configuration.bus.subscriptions.MyChannel.MyTopic[0] !== undefined; + subscription.unsubscribe(); + subExistsAfter = postal.configuration.bus.subscriptions.MyChannel.MyTopic.length !== 0; } ); - subscription = postal.channel( "MyChannel" ).subscribe( "MyTopic" , function () {} ); - subExistsBefore = postal.configuration.bus.subscriptions.MyChannel.MyTopic[0] !== undefined; - subscription.unsubscribe(); - subExistsAfter = postal.configuration.bus.subscriptions.MyChannel.MyTopic.length !== 0; - } ); - after( function () { - systemSubscription.unsubscribe(); - postal.utils.reset(); - } ); - it( "subscription should exist before unsubscribe", function () { - expect( subExistsBefore ).to.be.ok(); - } ); - it( "subscription should not exist after unsubscribe", function () { - expect( subExistsAfter ).to.not.be.ok() - } ); - it( "should have captured unsubscription creation event", function () { - expect( caughtUnsubscribeEvent ).to.be.ok(); - } ); + after( function () { + systemSubscription.unsubscribe(); + postal.utils.reset(); + } ); + it( "subscription should exist before unsubscribe", function () { + expect( subExistsBefore ).to.be.ok(); + } ); + it( "subscription should not exist after unsubscribe", function () { + expect( subExistsAfter ).to.not.be.ok() + } ); + it( "should have captured unsubscription creation event", function () { + expect( caughtUnsubscribeEvent ).to.be.ok(); + } ); + }); + describe( "With multiple subscribers on one channel", function() { + var subscription1, subscription2, results = []; + before( function () { + channel = postal.channel(); + subscription1 = channel.subscribe('test', function() { + results.push('1 received message'); + }).once(); + + subscription2 = channel.subscribe('test', function() { + results.push('2 received message'); + }); + channel.publish('test'); + channel.publish('test'); + + }); + after( function () { + subscription2.unsubscribe(); + postal.utils.reset(); + }); + it( "should produce expected messages", function() { + expect( results.length ).to.be(3); + expect( results[0] ).to.be("1 received message"); + expect( results[1] ).to.be("2 received message"); + expect( results[2] ).to.be("2 received message"); + }); + }); } ); describe( "When publishing a message", function () { var msgReceivedCnt = 0, @@ -131,27 +158,27 @@ describe( "Postal", function () { expect( msgReceivedCnt ).to.be( 5 ); } ); } ); - describe( "When subscribing with once()", function () { - var msgReceivedCnt = 0; - before( function () { - channel = postal.channel( "MyChannel" ); - subscription = channel.subscribe( "MyTopic", function ( data ) { - msgReceivedCnt++; - } ).once(); - channel.publish( "MyTopic", "Testing123" ); - channel.publish( "MyTopic", "Testing123" ); - channel.publish( "MyTopic", "Testing123" ); - channel.publish( "MyTopic", "Testing123" ); - channel.publish( "MyTopic", "Testing123" ); - channel.publish( "MyTopic", "Testing123" ); - } ); - after( function () { - postal.utils.reset(); - } ); - it( "subscription callback should be invoked 1 time", function () { - expect( msgReceivedCnt ).to.be( 1 ); - } ); - } ); + describe( "When subscribing with once()", function () { + var msgReceivedCnt = 0; + before( function () { + channel = postal.channel( "MyChannel" ); + subscription = channel.subscribe( "MyTopic",function ( data ) { + msgReceivedCnt++; + } ).once(); + channel.publish( "MyTopic", "Testing123" ); + channel.publish( "MyTopic", "Testing123" ); + channel.publish( "MyTopic", "Testing123" ); + channel.publish( "MyTopic", "Testing123" ); + channel.publish( "MyTopic", "Testing123" ); + channel.publish( "MyTopic", "Testing123" ); + } ); + after( function () { + postal.utils.reset(); + } ); + it( "subscription callback should be invoked 1 time", function () { + expect( msgReceivedCnt ).to.be( 1 ); + } ); + } ); describe( "When subscribing and ignoring duplicates", function () { var subInvokedCnt = 0; before( function () { diff --git a/src/LocalBus.js b/src/LocalBus.js index f2b2014..ccaceae 100644 --- a/src/LocalBus.js +++ b/src/LocalBus.js @@ -1,5 +1,5 @@ var fireSub = function(subDef, envelope) { - if ( postal.configuration.resolver.compare( subDef.topic, envelope.topic ) ) { + if ( !subDef.inactive && postal.configuration.resolver.compare( subDef.topic, envelope.topic ) ) { if ( _.all( subDef.constraints, function ( constraint ) { return constraint.call( subDef.context, envelope.data, envelope ); } ) ) { @@ -10,6 +10,14 @@ var fireSub = function(subDef, envelope) { } }; +var pubInProgress = false; +var unSubQueue = []; +var clearUnSubQueue = function() { + while(unSubQueue.length) { + unSubQueue.shift().unsubscribe(); + } +}; + var localBus = { addWireTap : function ( callback ) { var self = this; @@ -23,20 +31,22 @@ var localBus = { }, publish : function ( envelope ) { + pubInProgress = true; envelope.timeStamp = new Date(); _.each( this.wireTaps, function ( tap ) { tap( envelope.data, envelope ); } ); if ( this.subscriptions[envelope.channel] ) { - _.each( this.subscriptions[envelope.channel], function ( subscribers ) { - var idx = 0, len = subscribers.length, subDef; - while(idx < len) { - if( subDef = subscribers[idx++] ){ - fireSub(subDef, envelope); - } - } - } ); + _.each( this.subscriptions[envelope.channel], function ( subscribers ) { + var idx = 0, len = subscribers.length, subDef; + while ( idx < len ) { + if ( subDef = subscribers[idx++] ) { + fireSub( subDef, envelope ); + } + } + } ); } + pubInProgress = false; return envelope; }, @@ -71,14 +81,19 @@ var localBus = { wireTaps : [], unsubscribe : function ( config ) { + if(pubInProgress) { + unSubQueue.push(config); + return; + } if ( this.subscriptions[config.channel][config.topic] ) { var len = this.subscriptions[config.channel][config.topic].length, idx = 0; - for ( ; idx < len; idx++ ) { + while(idx < len) { if ( this.subscriptions[config.channel][config.topic][idx] === config ) { this.subscriptions[config.channel][config.topic].splice( idx, 1 ); break; } + idx += 1; } } } diff --git a/src/SubscriptionDefinition.js b/src/SubscriptionDefinition.js index 30c9544..6adfc0d 100644 --- a/src/SubscriptionDefinition.js +++ b/src/SubscriptionDefinition.js @@ -18,6 +18,7 @@ var SubscriptionDefinition = function ( channel, topic, callback ) { SubscriptionDefinition.prototype = { unsubscribe : function () { + this.inactive = true; postal.configuration.bus.unsubscribe( this ); postal.configuration.bus.publish( { channel : SYSTEM_CHANNEL, @@ -68,6 +69,7 @@ SubscriptionDefinition.prototype = { once : function () { this.disposeAfter( 1 ); + return this; }, withConstraint : function ( predicate ) {