├── branding ├── logo.png └── logo.sketch ├── iframe ├── pubsub-bridge.js ├── frame1.html ├── frame2.html └── index.html ├── test ├── .eslintrc.js ├── test-getSubscriptions.js ├── test-symbol.js ├── test-clearSubscriptions.js ├── test-subscribeAll.js ├── test-subscribeOnce.js ├── test-countSubscriptions.js ├── test-issue-54.js ├── test-bug-9.js ├── helper.js ├── test-subscribe.js ├── test-unsubscribe.js ├── test-publish.js └── test-hierarchical-addressing.js ├── .gitignore ├── docs ├── template.hbs └── docs.md ├── .npmignore ├── .travis.yml ├── composer.json ├── .editorconfig ├── .eslintrc.js ├── .github └── FUNDING.yml ├── CONTRIBUTING.md ├── LICENSE.md ├── package.json ├── CHANGES.md ├── README.md └── src └── pubsub.js /branding/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mroderick/PubSubJS/HEAD/branding/logo.png -------------------------------------------------------------------------------- /iframe/pubsub-bridge.js: -------------------------------------------------------------------------------- 1 | function setPubSub( PubSub ){ 2 | window.PubSub = PubSub; 3 | } -------------------------------------------------------------------------------- /branding/logo.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mroderick/PubSubJS/HEAD/branding/logo.sketch -------------------------------------------------------------------------------- /test/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "commonjs": true, 4 | "mocha": true, 5 | "node": true 6 | } 7 | }; 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Aptana files # 2 | ################ 3 | .project 4 | .settings/ 5 | .tmp* 6 | jquery.pubsub.js 7 | /node_modules/ 8 | /out/coverage/ 9 | .nyc_output/ 10 | coverage 11 | # yarn lock file 12 | yarn.lock -------------------------------------------------------------------------------- /docs/template.hbs: -------------------------------------------------------------------------------- 1 | Documentation is generated by [JSDoc](https://github.com/jsdoc3/jsdoc) and [jsdoc-to-markdown](https://github.com/jsdoc2md/jsdoc-to-markdown). 2 | 3 | *** 4 | 5 | {{>main}} 6 | 7 | * * * 8 | 9 | © 2018 [Morgan Roderick](https://roderick.dk), [http://roderick.dk/](https://roderick.dk) <[morgan@roderick.dk](mailto:morgan@roderick.dk)> 10 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # gitignore copy 2 | # Aptana files # 3 | ################ 4 | .project 5 | .settings/ 6 | .tmp* 7 | jquery.pubsub.js 8 | /node_modules/ 9 | /out/coverage/ 10 | .nyc_output/ 11 | coverage 12 | # yarn lock file 13 | yarn.lock 14 | 15 | # npmignore exclusive 16 | .editorconfig 17 | .travis.yml 18 | composer.json 19 | iframe/ 20 | CONTRIBUTING.md 21 | docs/ 22 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | cache: 4 | directories: 5 | - node_modules 6 | 7 | matrix: 8 | include: 9 | - node_js: "8" 10 | - node_js: "10" 11 | before_install: npm install coveralls 12 | before_script: npm run lint 13 | after_success: npm run coverage && cat ./coverage/lcov.info | coveralls lib; 14 | - node_js: "12" 15 | - node_js: "node" 16 | 17 | git: 18 | depth: 10 19 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mroderick/pubsubjs", 3 | "description": "Dependency free publish/subscribe for JavaScript", 4 | "keywords": [ 5 | "pubsub", 6 | "events", 7 | "publish", 8 | "subscribe" 9 | ], 10 | "homepage": "https://github.com/mroderick/PubSubJS", 11 | "type": "library", 12 | "license": "MIT", 13 | "authors": [ 14 | { 15 | "name": "Morgan Roderick", 16 | "email": "morgan@roderick.dk", 17 | "homepage": "http://roderick.dk", 18 | "role": "Developer" 19 | } 20 | ], 21 | "require": {} 22 | } 23 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | ; EditorConfig file: http://EditorConfig.org 2 | ; Install the "EditorConfig" plugin into your editor to use 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | insert_final_newline = true 8 | indent_style = space 9 | indent_size = 4 10 | trim_trailing_whitespace = true 11 | 12 | # Matches the exact files either package.json or .travis.yml 13 | [{package.json, .travis.yml}] 14 | indent_style = space 15 | indent_size = 2 16 | 17 | ; Needed if doing `git add --patch` to edit patches 18 | [*.diff] 19 | trim_trailing_whitespace = false 20 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "browser": true, 4 | "commonjs": true, 5 | "node": true 6 | }, 7 | "extends": "eslint:recommended", 8 | "rules": { 9 | "indent": [ 10 | "error", 11 | 4 12 | ], 13 | "linebreak-style": [ 14 | "error", 15 | "unix" 16 | ], 17 | "quotes": [ 18 | "error", 19 | "single" 20 | ], 21 | "semi": [ 22 | "error", 23 | "always" 24 | ] 25 | } 26 | }; -------------------------------------------------------------------------------- /test/test-getSubscriptions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var PubSub = require('../src/pubsub'), 4 | TestHelper = require('./helper'), 5 | assert = require('referee').assert, 6 | sinon = require('sinon'); 7 | 8 | describe('getSubscriptions method', function () { 9 | it('must be length eq 0', function () { 10 | var topic = TestHelper.getUniqueString(), 11 | spy1 = sinon.spy(); 12 | 13 | PubSub.subscribe(topic, spy1); 14 | 15 | var subscriptions = PubSub.getSubscriptions(topic).length; 16 | 17 | assert.equals(subscriptions,1); 18 | }); 19 | 20 | }); 21 | -------------------------------------------------------------------------------- /test/test-symbol.js: -------------------------------------------------------------------------------- 1 | /* global Symbol */ 2 | 'use strict'; 3 | 4 | var PubSub = require('../src/pubsub'), 5 | assert = require('referee').assert; 6 | 7 | describe( 'subscribe and publish', function() { 8 | before(function(){ 9 | if (typeof Symbol !== 'function'){ 10 | this.skip(); 11 | } 12 | }); 13 | it('should work on Symbol() type message/topic', function(){ 14 | var MESSAGE = Symbol('MESSAGE'); 15 | var func = function(){ return undefined; }; 16 | 17 | PubSub.subscribe( MESSAGE, func ); 18 | 19 | assert( PubSub.publish( MESSAGE ), true ); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /test/test-clearSubscriptions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var PubSub = require('../src/pubsub'), 4 | TestHelper = require('./helper'), 5 | refute = require('referee').refute, 6 | sinon = require('sinon'); 7 | 8 | describe('clearAllSubscriptions method', function () { 9 | it('must clear all subscriptions', function () { 10 | var topic = TestHelper.getUniqueString(), 11 | spy1 = sinon.spy(), 12 | spy2 = sinon.spy(); 13 | 14 | PubSub.subscribe(topic, spy1); 15 | PubSub.subscribe(topic, spy2); 16 | 17 | PubSub.clearAllSubscriptions(); 18 | 19 | PubSub.publishSync(topic, TestHelper.getUniqueString()); 20 | 21 | refute(spy1.called); 22 | refute(spy2.called); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /iframe/frame1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |frame1.html
26 | 27 | 28 | -------------------------------------------------------------------------------- /iframe/frame2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |frame2.html
26 | 27 | 28 | -------------------------------------------------------------------------------- /test/test-subscribeAll.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var PubSub = require('../src/pubsub'), 4 | TestHelper = require('../test/helper'), 5 | assert = require('referee').assert, 6 | sinon = require('sinon'); 7 | 8 | 9 | describe( 'subscribeAll method', function() { 10 | 11 | it('should return token as String', function(){ 12 | var func = function(){ return undefined; }, 13 | token = PubSub.subscribeAll( func ); 14 | 15 | assert.isString( token ); 16 | }); 17 | 18 | it('should subscribe for all messages', function() { 19 | var message = TestHelper.getUniqueString(), 20 | subscribeFn = sinon.spy(); 21 | 22 | PubSub.subscribeAll( subscribeFn ); 23 | PubSub.publishSync( message, 'some payload' ); 24 | 25 | assert( subscribeFn.calledOnce ); 26 | }); 27 | 28 | } ); 29 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [mroderick] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 12 | polar: # Replace with a single Polar username 13 | buy_me_a_coffee: # Replace with a single Buy Me a Coffee username 14 | thanks_dev: # 15 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 16 | -------------------------------------------------------------------------------- /test/test-subscribeOnce.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var PubSub = require('../src/pubsub'), 4 | TestHelper = require('../test/helper'), 5 | assert = require('referee').assert, 6 | sinon = require('sinon'); 7 | 8 | 9 | describe( 'subscribeOnce method', function() { 10 | 11 | it( 'should return PubSub', function() { 12 | var func = function(){ return undefined; }, 13 | message = TestHelper.getUniqueString(), 14 | pubSub = PubSub.subscribeOnce( message , func ); 15 | assert.same( pubSub, PubSub ); 16 | } ); 17 | 18 | it( 'must be executed only once', function() { 19 | 20 | var topic = TestHelper.getUniqueString(), 21 | spy = sinon.spy(); 22 | 23 | PubSub.subscribeOnce( topic, spy ); 24 | for ( var i = 0; i < 3; i++ ) { 25 | PubSub.publishSync( topic, TestHelper.getUniqueString() ); 26 | } 27 | 28 | assert( spy.calledOnce ); 29 | 30 | } ); 31 | 32 | } ); -------------------------------------------------------------------------------- /test/test-countSubscriptions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var PubSub = require('../src/pubsub'), 4 | TestHelper = require('./helper'), 5 | assert = require('referee').assert, 6 | sinon = require('sinon'); 7 | 8 | describe('test-countSubscriptions method', function () { 9 | it('must be count eq 0', function () { 10 | var topic = TestHelper.getUniqueString(), 11 | spy1 = sinon.spy(); 12 | 13 | PubSub.subscribe(topic, spy1); 14 | 15 | var counts = PubSub.countSubscriptions(topic); 16 | 17 | assert.equals(counts,1); 18 | }); 19 | 20 | it('should count all subscriptions', function() { 21 | var topic = TestHelper.getUniqueString(), 22 | spy1 = sinon.spy(), 23 | spy2 = sinon.spy(); 24 | 25 | PubSub.subscribe(topic, spy1); 26 | PubSub.subscribe(topic, spy2); 27 | 28 | var counts = PubSub.countSubscriptions(topic); 29 | 30 | assert.equals(counts, 2); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to PubSubJS 2 | 3 | We love pull requests. Here's a quick guide: 4 | 5 | 1. Fork the repo. 6 | 7 | 2. Run the tests. We only take pull requests with passing tests, and it's great 8 | to know that you have a clean slate 9 | 10 | 3. Add a test for your change. Only refactoring and documentation changes 11 | require no new tests. If you are adding functionality or fixing a bug, we need a test! 12 | 13 | 4. Make the test pass. 14 | 15 | 5. Push to your fork and submit a pull request. 16 | 17 | 18 | At this point you're waiting on us. We like to at least comment on, if not 19 | accept, pull requests within three business days (and, typically, one business 20 | day). We may suggest some changes or improvements or alternatives. 21 | 22 | * Use JavaScript idioms 23 | * Include tests that fail without your code, and pass with it 24 | * Update the documentation, the surrounding one, examples elsewhere, guides, 25 | whatever is affected by your contribution 26 | 27 | And in case we didn't emphasize it enough: we love tests! 28 | 29 | ## Syntax 30 | 31 | Install [Editor Config](http://editorconfig.org) for your text editor, this will ensure that the correct formatting is applied for each file type. 32 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright © `2010` [Morgan Roderick](https://roderick.dk), [http://roderick.dk/](https://roderick.dk) <[morgan@roderick.dk](mailto:morgan@roderick.dk)> 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | 10 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /iframe/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |14 | This is a small demonstration that shows that it is possible to use PubSubJS for communication across iframes. 15 |
16 | 17 | 18 | 19 | 20 | 21 | 40 | 41 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pubsub-js", 3 | "version": "1.9.5", 4 | "description": "Dependency free publish/subscribe library", 5 | "main": "./src/pubsub.js", 6 | "directories": { 7 | "lib": "src", 8 | "test": "test" 9 | }, 10 | "scripts": { 11 | "coverage": "nyc --reporter=lcov --reporter=text --reporter=json-summary npm test", 12 | "prepublishOnly": "jsdoc2md --template ./docs/template.hbs --files ./src/*.js > ./docs/docs.md", 13 | "lint": "eslint src/ test/", 14 | "test": "mocha", 15 | "preversion": "npm test", 16 | "version": "changes --commits --footer", 17 | "postversion": "git push --follow-tags && npm publish --access public" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git://github.com/mroderick/PubSubJS.git" 22 | }, 23 | "keywords": [ 24 | "pub/sub", 25 | "pubsub", 26 | "publish/subscribe", 27 | "publish", 28 | "subscribe" 29 | ], 30 | "author": { 31 | "name": "Morgan Roderick", 32 | "email": "morgan@roderick.dk", 33 | "url": "http://roderick.dk" 34 | }, 35 | "license": "MIT", 36 | "devDependencies": { 37 | "@studio/changes": "^2.0.0", 38 | "eslint": "4.19.1", 39 | "jsdoc-to-markdown": "^5.0.3", 40 | "mocha": "7.1.0", 41 | "nyc": "15.0.0", 42 | "referee": "^1.2.0", 43 | "sinon": "9.0.0" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /test/test-issue-54.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var PubSub = require('../src/pubsub'), 4 | TestHelper = require('../test/helper'), 5 | assert = require('referee').assert, 6 | sinon = require('sinon'); 7 | 8 | /** 9 | * This is a test proving that bug 54 has been fixed. 10 | * See https://github.com/mroderick/PubSubJS/issues/54 11 | */ 12 | describe( 'Issue 54, publish method', function () { 13 | 14 | it('should notify all subscribers, even when one is unsubscribed', function( done ){ 15 | var topic = TestHelper.getUniqueString(), 16 | token1, 17 | token1Unsubscribed = false, 18 | subscriber1 = function(){ 19 | PubSub.unsubscribe(token1); 20 | token1Unsubscribed = true; 21 | }, 22 | spy1 = sinon.spy(subscriber1), 23 | spy2 = sinon.spy(), 24 | spy3 = sinon.spy(), 25 | clock = sinon.useFakeTimers(); 26 | 27 | token1 = PubSub.subscribe( topic, spy1 ); 28 | PubSub.subscribe( topic, spy2 ); 29 | PubSub.subscribe( topic, spy3 ); 30 | 31 | PubSub.publish( topic ); 32 | 33 | clock.tick(1); 34 | 35 | assert( token1Unsubscribed === true ); 36 | assert( spy1.calledOnce ); 37 | assert( spy2.calledOnce ); 38 | assert( spy3.calledOnce ); 39 | 40 | done(); 41 | clock.restore(); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /test/test-bug-9.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var PubSub = require('../src/pubsub'), 4 | assert = require('referee').assert, 5 | sinon = require('sinon'); 6 | 7 | /** 8 | * This is a test proving that bug 9 has been fixed. 9 | * See https://github.com/mroderick/PubSubJS/issues/9 10 | */ 11 | describe( 'Bug 9, publish method', function() { 12 | it('should notify all subscribers in a hierarchy', function( done ){ 13 | var subscriber1 = sinon.spy(), 14 | subscriber2 = sinon.spy(), 15 | subscriber3 = sinon.spy(), 16 | clock = sinon.useFakeTimers(); 17 | 18 | PubSub.subscribe( 'a.b.c', subscriber1 ); 19 | PubSub.subscribe( 'a.b', subscriber2 ); 20 | PubSub.subscribe( 'a', subscriber3 ); 21 | 22 | PubSub.publish( 'a.b.c.d' ); 23 | 24 | clock.tick(1); 25 | 26 | assert( subscriber1.calledOnce ); 27 | assert( subscriber2.calledOnce ); 28 | assert( subscriber3.calledOnce ); 29 | 30 | done(); 31 | clock.restore(); 32 | }); 33 | 34 | it('should notify individual subscribers, even when there are no subscribers further up', function( done ){ 35 | 36 | var rootTopic = 'a.b.c', 37 | subscriber = sinon.spy(), 38 | clock = sinon.useFakeTimers(); 39 | 40 | PubSub.subscribe(rootTopic, subscriber); 41 | PubSub.publish(rootTopic + '.d'); 42 | 43 | clock.tick(1); 44 | 45 | assert( subscriber.calledOnce ); 46 | 47 | done(); 48 | clock.restore(); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /test/helper.js: -------------------------------------------------------------------------------- 1 | (function(root, factory){ 2 | 'use strict'; 3 | 4 | var define = root.define; 5 | 6 | // CommonJS 7 | if (typeof exports === 'object'){ 8 | module.exports = factory(); 9 | 10 | // AMD 11 | } else if (typeof define === 'function' && define.amd){ 12 | define(factory); 13 | // Browser 14 | } else { 15 | root.TestHelper = factory(); 16 | } 17 | }( ( typeof window === 'object' && window ) || this, function(){ 18 | 19 | 'use strict'; 20 | 21 | var TestHelper = {}, 22 | assert = require('referee').assert; 23 | 24 | // helps us make sure that the order of the tests have no impact on their succes 25 | function getUniqueString(){ 26 | if ( getUniqueString.uid === undefined ){ 27 | getUniqueString.uid = 0; 28 | } 29 | getUniqueString.uid++; 30 | 31 | return 'my unique String number ' + getUniqueString.uid.toString(); 32 | } 33 | 34 | // makes sure that all tokens in the passed array are different 35 | function assertAllTokensDifferent( tokens ){ 36 | var length = tokens.length, 37 | j, k; 38 | assert( tokens.length > 0 ); 39 | // compare all tokens 40 | for ( j = 0; j < length; j++ ){ 41 | for ( k = j + 1; k < length; k++ ){ 42 | assert( tokens[j] !== tokens[k] ); 43 | } 44 | } 45 | 46 | // make sure we actually tested something 47 | assert.equals( j, length ); 48 | assert.equals( k, length ); 49 | } 50 | 51 | TestHelper.getUniqueString = getUniqueString; 52 | TestHelper.assertAllTokensDifferent = assertAllTokensDifferent; 53 | 54 | return TestHelper; 55 | })); 56 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # Changes 2 | 3 | ## 1.9.5 4 | 5 | - [`ea4d477`](https://github.com/mroderick/PubSubJS/commit/ea4d477c593b0832092105e6ab9d985ea3cf301f) 6 | Add support for server side rendering (Artur Kudeł) 7 | 8 | _Released on 2024-10-28._ 9 | 10 | ## 1.9.4 11 | 12 | - [`ae3284d`](https://github.com/mroderick/PubSubJS/commit/ae3284d46054b189e143b405e1bfc6c09643bf77) 13 | Use existing root.pubSub when present (#213) (abishek-srinivasan) 14 | > 15 | > This means that loading `pubsub-js` more than once in the same environment will work as the author expects. A warning will be output on the console to make the developer aware of the duplication. 16 | 17 | _Released on 2021-11-11._ 18 | 19 | ## 1.9.3 20 | 21 | - [`a810919`](https://github.com/mroderick/PubSubJS/commit/a81091962dd4836da9da6dcf7aafeca4aeb9f815) 22 | Fix countSubscriptions 23 | > 24 | > This was introduced in ad93491c760ebc0429a7e9072b2747b2c2ce0a0a, but had 25 | > a bug that was not covered by unit tests. 26 | > 27 | 28 | _Released on 2021-02-18._ 29 | 30 | ## 1.9.2 31 | 32 | - [`83648dd`](https://github.com/mroderick/PubSubJS/commit/83648dd9e48762a8058904debe1b653850bbcf5c) 33 | fix #115 don't directly use hasOwnProperty (jianghaoran) 34 | 35 | _Released on 2020-12-14._ 36 | 37 | ## 1.9.1 38 | 39 | - [`ead7906`](https://github.com/mroderick/PubSubJS/commit/ead79069b79df8c4f7d3324047cdb3b9d4c33571) 40 | Fix amd module export (#173) (Sven Busse) 41 | > 42 | > Co-authored-by: Kevin <58685946+Kepeters@users.noreply.github.com> 43 | 44 | _Released on 2020-11-13._ 45 | 46 | ## 1.9.0 47 | 48 | - [`3fb21e3`](https://github.com/mroderick/PubSubJS/commit/3fb21e309f8bb9fd32906b25b3a607bc32e8b1a7) 49 | Add `subscribeAll` method (Subin Varghese) 50 | 51 | _Released on 2020-08-17._ 52 | 53 | ## 1.8.0 54 | 55 | * Add `getSubscriptions` 56 | * Add countSubscriptions 57 | 58 | _Released on 2019-12-20._ 59 | -------------------------------------------------------------------------------- /test/test-subscribe.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var PubSub = require('../src/pubsub'), 4 | TestHelper = require('../test/helper'), 5 | assert = require('referee').assert, 6 | refute = require('referee').refute; 7 | 8 | describe( 'subscribe method', function() { 9 | it('should return token as String', function(){ 10 | var func = function(){ return undefined; }, 11 | message = TestHelper.getUniqueString(), 12 | token = PubSub.subscribe( message , func ); 13 | 14 | assert.isString( token ); 15 | }); 16 | 17 | it('should return new token for several subscriptions with same function', function(){ 18 | var func = function(){ return undefined; }, 19 | tokens = [], 20 | iterations = 10, 21 | message = TestHelper.getUniqueString(), 22 | i; 23 | 24 | // build an array of tokens 25 | for ( i = 0; i < iterations; i++ ){ 26 | tokens.push( PubSub.subscribe( message, func ) ); 27 | } 28 | // make sure all tokens are different 29 | TestHelper.assertAllTokensDifferent( tokens ); 30 | }); 31 | 32 | it('should return unique tokens for each namespaced subscription', function(){ 33 | var func = function(){ return undefined; }, 34 | tokens = [], 35 | messages = ['library', 'library.music', 'library.music.jazz'], 36 | i; 37 | 38 | // build an array of tokens 39 | for ( i = 0; i < messages.length; i++ ){ 40 | tokens.push( PubSub.subscribe( messages[i], func ) ); 41 | } 42 | // make sure all tokens are different 43 | TestHelper.assertAllTokensDifferent( tokens ); 44 | }); 45 | 46 | it('should return unique token for unique functions', function(){ 47 | var tokens = [], 48 | iterations = 10, 49 | message = TestHelper.getUniqueString(), 50 | i; 51 | 52 | function bakeFunc( value ){ 53 | return function(){ 54 | return value; 55 | }; 56 | } 57 | 58 | // build an array of tokens, passing in a different function for each subscription 59 | for ( i = 0; i < iterations; i++ ){ 60 | tokens.push( PubSub.subscribe( message, bakeFunc( i ) ) ); 61 | } 62 | 63 | // make sure all tokens are different 64 | TestHelper.assertAllTokensDifferent( tokens ); 65 | }); 66 | 67 | it('should return false when subscriber argument is not a function', function(){ 68 | var invalidSubscribers = [undefined, null, 'a string', 123, [], {}, new Date()], 69 | topic = TestHelper.getUniqueString(), 70 | i; 71 | 72 | for ( i = 0; i < invalidSubscribers.length; i++ ){ 73 | assert.equals(PubSub.subscribe(topic, invalidSubscribers[i]), false); 74 | } 75 | 76 | assert.equals(i, invalidSubscribers.length); 77 | }); 78 | 79 | it('must not throw errors when publishing with invalid subscribers', function(){ 80 | var invalidSubscribers = [undefined, null, 'a string', 123, [], {}, new Date()], 81 | topic = TestHelper.getUniqueString(), 82 | i; 83 | 84 | for (i = 0; i < invalidSubscribers.length; i++){ 85 | PubSub.subscribe(topic, invalidSubscribers[i]); 86 | } 87 | 88 | refute.exception(function(){ 89 | PubSub.publish(topic, TestHelper.getUniqueString()); 90 | }); 91 | }); 92 | }); 93 | -------------------------------------------------------------------------------- /test/test-unsubscribe.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var PubSub = require('../src/pubsub'), 4 | TestHelper = require('../test/helper'), 5 | assert = require('referee').assert, 6 | refute = require('referee').refute, 7 | sinon = require('sinon'); 8 | 9 | describe( 'unsubscribe method', function() { 10 | it('should return token when succesful', function(){ 11 | var func = function(){ return undefined; }, 12 | message = TestHelper.getUniqueString(), 13 | token = PubSub.subscribe( message, func), 14 | result = PubSub.unsubscribe( token ); 15 | 16 | assert.equals( result, token ); 17 | }); 18 | 19 | it('should return false when unsuccesful', function(){ 20 | var unknownToken = 'my unknown token', 21 | result = PubSub.unsubscribe( unknownToken ), 22 | func = function(){ return undefined; }, 23 | message = TestHelper.getUniqueString(), 24 | token = PubSub.subscribe( message, func ); 25 | 26 | // first, let's try a completely unknown token 27 | assert.equals( result, false ); 28 | 29 | // now let's try unsubscribing the same method twice 30 | PubSub.unsubscribe( token ); 31 | assert.equals( PubSub.unsubscribe( token ), false ); 32 | }); 33 | 34 | it('with function argument should return true when succesful', function(){ 35 | var func = function(){ return undefined; }, 36 | message = TestHelper.getUniqueString(), 37 | result; 38 | 39 | PubSub.subscribe( message, func); 40 | result = PubSub.unsubscribe( func ); 41 | 42 | assert.equals( result, true ); 43 | }); 44 | 45 | it('with function argument should return false when unsuccesful', function(){ 46 | var func = function(){ return undefined; }, 47 | message = TestHelper.getUniqueString(), 48 | unknownToken = 'my unknown token', 49 | result = PubSub.unsubscribe( unknownToken ); 50 | 51 | // first, let's try a completely unknown token 52 | 53 | assert.equals( result, false ); 54 | 55 | // now let's try unsubscribing the same method twice 56 | PubSub.subscribe( message, func ); 57 | PubSub.subscribe( message, func ); 58 | PubSub.subscribe( message, func ); 59 | 60 | // unsubscribe once, this should remove all subscriptions for message 61 | PubSub.unsubscribe( func ); 62 | 63 | // unsubscribe again 64 | assert.equals( PubSub.unsubscribe( func ), false ); 65 | }); 66 | 67 | it('with topic argument, must clear all exactly matched subscriptions', function(){ 68 | var topic = TestHelper.getUniqueString(), 69 | spy1 = sinon.spy(), 70 | spy2 = sinon.spy(); 71 | 72 | PubSub.subscribe(topic, spy1); 73 | PubSub.subscribe(topic, spy2); 74 | 75 | PubSub.unsubscribe(topic); 76 | 77 | PubSub.publishSync(topic, TestHelper.getUniqueString()); 78 | 79 | refute(spy1.called); 80 | refute(spy2.called); 81 | }); 82 | 83 | it('with topic argument, must only clear matched subscriptions', function(){ 84 | var topic1 = TestHelper.getUniqueString(), 85 | topic2 = TestHelper.getUniqueString(), 86 | spy1 = sinon.spy(), 87 | spy2 = sinon.spy(); 88 | 89 | PubSub.subscribe(topic1, spy1); 90 | PubSub.subscribe(topic2, spy2); 91 | 92 | PubSub.unsubscribe(topic1); 93 | 94 | PubSub.publishSync(topic1, TestHelper.getUniqueString()); 95 | PubSub.publishSync(topic2, TestHelper.getUniqueString()); 96 | 97 | refute(spy1.called); 98 | assert(spy2.called); 99 | }); 100 | 101 | it('with topic argument, must clear all matched hierarchical subscriptions', function(){ 102 | var topic = TestHelper.getUniqueString(), 103 | topicA = topic + '.a', 104 | topicB = topic + '.a.b', 105 | topicC = topic + '.a.b.c', 106 | spyA = sinon.spy(), 107 | spyB = sinon.spy(), 108 | spyC = sinon.spy(); 109 | 110 | PubSub.subscribe(topicA, spyA); 111 | PubSub.subscribe(topicB, spyB); 112 | PubSub.subscribe(topicC, spyC); 113 | 114 | PubSub.unsubscribe(topicB); 115 | 116 | PubSub.publishSync(topicC, TestHelper.getUniqueString()); 117 | 118 | assert(spyA.called); 119 | refute(spyB.called); 120 | refute(spyC.called); 121 | }); 122 | 123 | it('with parent topic argument, must clear all child subscriptions', function() { 124 | var topic = TestHelper.getUniqueString(), 125 | topicA = topic + '.a', 126 | topicB = topic + '.a.b', 127 | topicC = topic + '.a.b.c', 128 | spyB = sinon.spy(), 129 | spyC = sinon.spy(); 130 | 131 | // subscribe only to children: 132 | PubSub.subscribe(topicB, spyB); 133 | PubSub.subscribe(topicC, spyC); 134 | 135 | // but unsubscribe from a parent: 136 | PubSub.unsubscribe(topicA); 137 | 138 | PubSub.publishSync(topicB, TestHelper.getUniqueString()); 139 | PubSub.publishSync(topicC, TestHelper.getUniqueString()); 140 | 141 | refute(spyB.called); 142 | refute(spyC.called); 143 | }); 144 | 145 | it('must not throw exception when unsubscribing as part of publishing', function(){ 146 | refute.exception(function(){ 147 | var topic = TestHelper.getUniqueString(), 148 | sub1 = function(){ 149 | PubSub.unsubscribe(sub1); 150 | }, 151 | sub2 = function(){ return undefined; }; 152 | 153 | PubSub.subscribe( topic, sub1 ); 154 | PubSub.subscribe( topic, sub2 ); 155 | 156 | PubSub.publishSync( topic, 'hello world!' ); 157 | }); 158 | }); 159 | }); 160 | -------------------------------------------------------------------------------- /docs/docs.md: -------------------------------------------------------------------------------- 1 | Documentation is generated by [JSDoc](https://github.com/jsdoc3/jsdoc) and [jsdoc-to-markdown](https://github.com/jsdoc2md/jsdoc-to-markdown). 2 | 3 | *** 4 | 5 | ## Functions 6 | 7 |Returns a function that throws the passed exception, for use as argument for setTimeout
10 |BooleanPublishes the message, passing the data to it's subscribers
13 |BooleanPublishes the message synchronously, passing the data to it's subscribers
16 |StringSubscribes the passed function to the passed message. Every returned token is unique and should be stored if you need to unsubscribe
19 |PubSubSubscribes the passed function to the passed message once
22 |Clears all subscriptions
25 |intClear subscriptions by the topic
28 |ArrayCount subscriptions by the topic
31 |Gets subscriptions by the topic
34 |Removes subscriptions
37 |When passed a token, removes a specific subscription.
39 |When passed a function, removes all subscriptions for that function
41 |When passed a topic, removes all subscriptions for that topic (hierarchy)
43 |Object | An Error object |
58 |
59 |
60 |
61 | ## publish(message, data) ⇒ Boolean
62 | Publishes the message, passing the data to it's subscribers
63 |
64 | **Kind**: global function
65 |
66 | | Param | Type | Description |
67 | | --- | --- | --- |
68 | | message | String | The message to publish |
69 | | data | | The data to pass to subscribers |
70 |
71 |
72 |
73 | ## publishSync(message, data) ⇒ Boolean
74 | Publishes the message synchronously, passing the data to it's subscribers
75 |
76 | **Kind**: global function
77 |
78 | | Param | Type | Description |
79 | | --- | --- | --- |
80 | | message | String | The message to publish |
81 | | data | | The data to pass to subscribers |
82 |
83 |
84 |
85 | ## subscribe(message, func) ⇒ String
86 | Subscribes the passed function to the passed message. Every returned token is unique and should be stored if you need to unsubscribe
87 |
88 | **Kind**: global function
89 |
90 | | Param | Type | Description |
91 | | --- | --- | --- |
92 | | message | String | The message to subscribe to |
93 | | func | function | The function to call when a new message is published |
94 |
95 |
96 |
97 | ## subscribeOnce(message, func) ⇒ PubSub
98 | Subscribes the passed function to the passed message once
99 |
100 | **Kind**: global function
101 |
102 | | Param | Type | Description |
103 | | --- | --- | --- |
104 | | message | String | The message to subscribe to |
105 | | func | function | The function to call when a new message is published |
106 |
107 |
108 |
109 | ## clearAllSubscriptions()
110 | Clears all subscriptions
111 |
112 | **Kind**: global function
113 | **Access**: public
114 |
115 |
116 | ## clearAllSubscriptions() ⇒ int
117 | Clear subscriptions by the topic
118 |
119 | **Kind**: global function
120 | **Access**: public
121 |
122 |
123 | ## countSubscriptions() ⇒ Array
124 | Count subscriptions by the topic
125 |
126 | **Kind**: global function
127 | **Access**: public
128 |
129 |
130 | ## getSubscriptions()
131 | Gets subscriptions by the topic
132 |
133 | **Kind**: global function
134 | **Access**: public
135 |
136 |
137 | ## subscribeOnce(value)
138 | Removes subscriptions
139 |
140 | - When passed a token, removes a specific subscription.
141 |
142 | - When passed a function, removes all subscriptions for that function
143 |
144 | - When passed a topic, removes all subscriptions for that topic (hierarchy)
145 |
146 | **Kind**: global function
147 | **Access**: public
148 |
149 | | Param | Type | Description |
150 | | --- | --- | --- |
151 | | value | String \| function | A token, function or topic to unsubscribe from |
152 |
153 | **Example**
154 | ```js
155 | // Unsubscribing with a token
156 | var token = PubSub.subscribe('mytopic', myFunc);
157 | PubSub.unsubscribe(token);
158 | ```
159 | **Example**
160 | ```js
161 | // Unsubscribing with a function
162 | PubSub.unsubscribe(myFunc);
163 | ```
164 | **Example**
165 | ```js
166 | // Unsubscribing from a topic
167 | PubSub.unsubscribe('mytopic');
168 | ```
169 |
170 | * * *
171 |
172 | © 2018 [Morgan Roderick](https://roderick.dk), [http://roderick.dk/](https://roderick.dk) <[morgan@roderick.dk](mailto:morgan@roderick.dk)>
173 |
--------------------------------------------------------------------------------
/test/test-publish.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var PubSub = require('../src/pubsub'),
4 | TestHelper = require('../test/helper'),
5 | assert = require('referee').assert,
6 | refute = require('referee').refute,
7 | sinon = require('sinon');
8 |
9 | describe( 'publish method', function () {
10 | it('should return false if there are no subscribers', function(){
11 | var message = TestHelper.getUniqueString();
12 | assert.equals( PubSub.publish( message ), false );
13 | });
14 |
15 | it('should return true if there are subscribers to a message', function(){
16 | var message = TestHelper.getUniqueString(),
17 | func = function(){ return undefined; };
18 |
19 | PubSub.subscribe( message, func );
20 | assert( PubSub.publish( message ) );
21 | });
22 |
23 | it('should return false, when there are no longer any subscribers to a message', function(){
24 | var message = TestHelper.getUniqueString(),
25 | func = function(){ return undefined; },
26 | token = PubSub.subscribe(message, func);
27 |
28 | PubSub.unsubscribe(token);
29 | assert.equals( PubSub.publish(message), false );
30 | });
31 |
32 | it('should call all subscribers for a message exactly once', function(){
33 | var message = TestHelper.getUniqueString(),
34 | spy1 = sinon.spy(),
35 | spy2 = sinon.spy();
36 |
37 | PubSub.subscribe( message, spy1 );
38 | PubSub.subscribe( message, spy2 );
39 |
40 | PubSub.publishSync( message, 'my payload' ); // force sync here, easier to test
41 |
42 | assert( spy1.calledOnce );
43 | assert( spy2.calledOnce );
44 | });
45 |
46 | it('should call all ONLY subscribers of the published message', function(){
47 | var message1 = TestHelper.getUniqueString(),
48 | message2 = TestHelper.getUniqueString(),
49 | spy1 = sinon.spy(),
50 | spy2 = sinon.spy();
51 |
52 | PubSub.subscribe( message1, spy1 );
53 | PubSub.subscribe( message2, spy2 );
54 |
55 | PubSub.publishSync( message1, 'some payload' );
56 |
57 | // ensure the first subscriber IS called
58 | assert( spy1.called );
59 | // ensure the second subscriber IS NOT called
60 | assert.equals( spy2.callCount, 0 );
61 | });
62 |
63 | it('should call subscribers with message as first argument', function(){
64 | var message = TestHelper.getUniqueString(),
65 | spy = sinon.spy();
66 |
67 | PubSub.subscribe( message, spy );
68 | PubSub.publishSync( message, 'some payload' );
69 |
70 | assert( spy.calledWith( message ) );
71 | });
72 |
73 | it('should call subscribers with data as second argument', function(){
74 | var message = TestHelper.getUniqueString(),
75 | spy = sinon.spy(),
76 | data = TestHelper.getUniqueString();
77 |
78 | PubSub.subscribe( message, spy );
79 | PubSub.publishSync( message, data );
80 |
81 | assert( spy.calledWith( message, data ) );
82 | });
83 |
84 | it('should publish method asyncronously', function( done ){
85 | var message = TestHelper.getUniqueString(),
86 | spy = sinon.spy(),
87 | data = TestHelper.getUniqueString(),
88 | clock = sinon.useFakeTimers();
89 |
90 | PubSub.subscribe( message, spy );
91 | PubSub.publish( message, data );
92 |
93 | assert.equals( spy.callCount, 0 );
94 | clock.tick(1);
95 | assert.equals( spy.callCount, 1 );
96 |
97 | done();
98 | clock.restore();
99 | });
100 |
101 | it('publishSync method should allow syncronous publication', function(){
102 | var message = TestHelper.getUniqueString(),
103 | spy = sinon.spy(),
104 | data = TestHelper.getUniqueString();
105 |
106 | PubSub.subscribe( message, spy );
107 | PubSub.publishSync( message, data );
108 |
109 | assert.equals( spy.callCount, 1 );
110 | });
111 |
112 | it('should call all subscribers, even if there are exceptions', function( done ){
113 | var message = TestHelper.getUniqueString(),
114 | func1 = function(){
115 | throw('some error');
116 | },
117 | spy1 = sinon.spy(),
118 | spy2 = sinon.spy(),
119 | clock = sinon.useFakeTimers();
120 |
121 | PubSub.subscribe( message, func1 );
122 | PubSub.subscribe( message, spy1 );
123 | PubSub.subscribe( message, spy2 );
124 |
125 | assert.exception( function(){
126 | PubSub.publishSync( message, 'some data' );
127 | clock.tick(1);
128 | });
129 |
130 | assert( spy1.called );
131 | assert( spy2.called );
132 |
133 | done();
134 | clock.restore();
135 | });
136 |
137 | it('should fail immediately on exceptions when immediateExceptions is true', function(){
138 | var message = TestHelper.getUniqueString(),
139 | func1 = function(){
140 | throw('some error');
141 | },
142 | spy1 = sinon.spy(),
143 | spy2 = sinon.spy();
144 |
145 |
146 | PubSub.subscribe( message, func1 );
147 | PubSub.subscribe( message, spy1 );
148 |
149 | PubSub.immediateExceptions = true;
150 |
151 | assert.exception( function(){
152 | PubSub.publishSync( message, 'some data' );
153 | });
154 |
155 | refute( spy1.called );
156 | refute( spy2.called );
157 |
158 | // make sure we restore PubSub to it's original state
159 | delete PubSub.immediateExceptions;
160 | });
161 |
162 | it('should fail immediately on exceptions in namespaces when immediateExceptions is true', function(){
163 | var func1 = function(){
164 | throw('some error');
165 | },
166 | spy1 = sinon.spy();
167 |
168 | PubSub.subscribe( 'buy', func1 );
169 | PubSub.subscribe( 'buy', spy1 );
170 |
171 | PubSub.immediateExceptions = true;
172 |
173 | assert.exception( function(){
174 | PubSub.publishSync( 'buy.tomatoes', 'some data' );
175 | });
176 |
177 | refute( spy1.called );
178 |
179 | // make sure we restore PubSub to it's original state
180 | delete PubSub.immediateExceptions;
181 | });
182 |
183 | it('should call all subscribers, even when there are unsubscriptions within', function(done){
184 | var topic = TestHelper.getUniqueString(),
185 | spy1 = sinon.spy(),
186 | func1 = function func1(){
187 | PubSub.unsubscribe(func1);
188 | spy1();
189 | },
190 |
191 | spy2 = sinon.spy(),
192 | func2 = function func2(){
193 | PubSub.unsubscribe(func2);
194 | spy2();
195 | },
196 |
197 | clock = sinon.useFakeTimers();
198 |
199 | PubSub.subscribe(topic, func1);
200 | PubSub.subscribe(topic, func2);
201 |
202 | PubSub.publish(topic, 'some data');
203 | clock.tick(1);
204 |
205 | assert(spy1.called, 'expected spy1 to be called');
206 | assert(spy2.called, 'expected spy2 to be called');
207 |
208 | clock.restore();
209 | done();
210 | });
211 | });
212 |
--------------------------------------------------------------------------------
/test/test-hierarchical-addressing.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var PubSub = global.PubSub || require('../src/pubsub'),
4 | TestHelper = global.TestHelper || require('../test/helper'),
5 | assert = require('referee').assert,
6 | sinon = require('sinon');
7 |
8 | describe( 'Hierarchical addressing', function () {
9 | beforeEach(function(){
10 | this.clock = sinon.useFakeTimers();
11 | });
12 |
13 | afterEach(function(){
14 | this.clock.restore();
15 | });
16 |
17 | it('publish method should not call any children in a namespace', function( done ) {
18 | var messages = ['library', 'library.music'],
19 | spy = sinon.spy(),
20 | data = TestHelper.getUniqueString();
21 |
22 |
23 | PubSub.subscribe( messages[0], spy ); //This should be called
24 | PubSub.subscribe( messages[1], spy );
25 | PubSub.publish( messages[0], data );
26 |
27 | assert.equals( spy.callCount, 0 );
28 | this.clock.tick(1);
29 | assert.equals( spy.callCount, 1 );
30 |
31 | done();
32 | });
33 |
34 | it('publish method should call a parent namespace', function( done ) {
35 | // Publishing library.music should trigger parent library
36 | var messages = ['library', 'library.music'],
37 | spy = sinon.spy(),
38 | data = TestHelper.getUniqueString();
39 |
40 |
41 | PubSub.subscribe( messages[0], spy ); //This should be called
42 | PubSub.subscribe( messages[1], spy ); //This should be called
43 | PubSub.publish( messages[1], data );
44 |
45 | assert.equals( spy.callCount, 0 );
46 | this.clock.tick(1);
47 | assert.equals( spy.callCount, 2 );
48 |
49 | done();
50 | });
51 |
52 | it('publish method should call only a parent namespace', function( done ) {
53 | //Publishing library.music should only trigger parents descendants
54 | //Even if it has a child
55 | var messages = ['library', 'library.music', 'library.music.jazz'],
56 | spy = sinon.spy(),
57 | data = TestHelper.getUniqueString();
58 |
59 |
60 | PubSub.subscribe( messages[0], spy ); //This should be called
61 | PubSub.subscribe( messages[1], spy ); //This should be called
62 | PubSub.subscribe( messages[2], spy );
63 | PubSub.publish( messages[1], data );
64 |
65 | assert.equals( spy.callCount, 0 );
66 | this.clock.tick(1);
67 | assert.equals( spy.callCount, 2 );
68 |
69 | done();
70 | });
71 |
72 | it('publish method should call all parent namespaces', function( done ) {
73 | //Publishing library.music.jazz should trigger all parents
74 | var messages = ['library', 'library.music', 'library.music.jazz'],
75 | spy = sinon.spy(),
76 | data = TestHelper.getUniqueString();
77 |
78 |
79 | PubSub.subscribe( messages[0], spy ); //This should be called
80 | PubSub.subscribe( messages[1], spy ); //This should be called
81 | PubSub.subscribe( messages[2], spy ); //This should be called
82 | PubSub.publish( messages[2], data );
83 |
84 | assert.equals( spy.callCount, 0 );
85 | this.clock.tick(1);
86 | assert.equals( spy.callCount, 3 );
87 |
88 | done();
89 | });
90 |
91 | it('publish method should call only parent descendants', function( done ) {
92 | //Publishing library.music.jazz should trigger only all parents descendants
93 | //Skipping library.playlist and library.playlist.*
94 | var messages = [
95 | 'library',
96 | 'library.music',
97 | 'library.music.jazz',
98 | 'library.playlist',
99 | 'library.playlist.mine'
100 | ],
101 | spy = sinon.spy(),
102 | data = TestHelper.getUniqueString();
103 |
104 | PubSub.subscribe( messages[0], spy ); //This should be called
105 | PubSub.subscribe( messages[1], spy ); //This should be called
106 | PubSub.subscribe( messages[2], spy ); //This should be called
107 | PubSub.subscribe( messages[3], spy );
108 | PubSub.subscribe( messages[4], spy );
109 | PubSub.publish( messages[2], data );
110 |
111 | assert.equals( spy.callCount, 0 );
112 | this.clock.tick(1);
113 | assert.equals( spy.callCount, 3 );
114 |
115 | done();
116 | });
117 |
118 | it('publish method should call all parent descendants deeply', function( done ) {
119 | //Publishing library.music.jazz.soft.swing should trigger all but
120 | //library.music.playlist.jazz
121 | var messages = [
122 | 'library',
123 | 'library.music',
124 | 'library.music.jazz',
125 | 'library.music.jazz.soft',
126 | 'library.music.jazz.soft.swing',
127 | 'library.music.playlist.jazz'
128 | ],
129 | spy = sinon.spy(),
130 | data = TestHelper.getUniqueString();
131 |
132 |
133 | PubSub.subscribe( messages[0], spy ); //This should be called
134 | PubSub.subscribe( messages[1], spy ); //This should be called
135 | PubSub.subscribe( messages[2], spy ); //This should be called
136 | PubSub.subscribe( messages[3], spy ); //This should be called
137 | PubSub.subscribe( messages[4], spy ); //This should be called
138 | PubSub.subscribe( messages[5], spy ); //This should be called
139 | PubSub.subscribe( messages[6], spy );
140 | PubSub.publish( messages[4], data );
141 |
142 | assert.equals( spy.callCount, 0 );
143 | this.clock.tick(1);
144 | assert.equals( spy.callCount, 5 );
145 |
146 | done();
147 | });
148 |
149 | it('publish method should still call all parents, even when middle child is unsubscribed', function( done ) {
150 | var messages = ['library', 'library.music', 'library.music.jazz'],
151 | spy = sinon.spy(),
152 | data = TestHelper.getUniqueString(),
153 | token;
154 |
155 |
156 | PubSub.subscribe( messages[0], spy ); //This should be called
157 | PubSub.subscribe( messages[2], spy ); //This should be called
158 |
159 | token = PubSub.subscribe( messages[1], spy );
160 |
161 | PubSub.unsubscribe( token ); //Take out middle child
162 |
163 | PubSub.publish( messages[2], data );
164 |
165 | assert.equals( spy.callCount, 0 );
166 | this.clock.tick(1);
167 | assert.equals( spy.callCount, 2 );
168 |
169 | done();
170 | });
171 |
172 | it('unsubscribe method should return tokens when succesfully removing namespaced message', function(){
173 | var func = function(){ return undefined; },
174 | messages = ['playlist.music', 'playlist.music.jazz'],
175 | token1 = PubSub.subscribe( messages[0], func),
176 | token2 = PubSub.subscribe( messages[1], func ),
177 | result1 = PubSub.unsubscribe( token1 ),
178 | result2 = PubSub.unsubscribe( token2 );
179 |
180 | assert.equals( result1, token1 );
181 | assert.equals( result2, token2 );
182 | });
183 |
184 | it('unsubscribe method should unsubscribe parent without affecting orphans', function( done ){
185 | var data = TestHelper.getUniqueString(),
186 | spy = sinon.spy(),
187 | messages = ['playlist', 'playlist.music', 'playlist.music.jazz'],
188 | token;
189 |
190 | token = PubSub.subscribe( messages[0], spy ); //Gets unsubscribed
191 | PubSub.subscribe( messages[1], spy ); //This should be called
192 | PubSub.subscribe( messages[2], spy ); //This should be called
193 |
194 | PubSub.unsubscribe( token );
195 |
196 | PubSub.publish( messages[2], data );
197 |
198 | assert.equals( spy.callCount, 0 );
199 | this.clock.tick(1);
200 | assert.equals( spy.callCount, 2 );
201 |
202 | done();
203 | });
204 | });
205 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
13 |
14 | PubSubJS is a [topic-based](http://en.wikipedia.org/wiki/Publish–subscribe_pattern#Message_filtering) [publish/subscribe](http://en.wikipedia.org/wiki/Publish/subscribe) library written in JavaScript.
15 |
16 | PubSubJS has synchronisation decoupling, so topics are published asynchronously. This helps keep your program predictable as the originator of topics will not be blocked while consumers process them.
17 |
18 | For the adventurous, PubSubJS also supports synchronous topic publication. This can give a speedup in some environments (browsers, not all), but can also lead to some very difficult to reason about programs, where one topic triggers publication of another topic in the same execution chain.
19 |
20 | #### Single process
21 |
22 | PubSubJS is designed to be used within a **single process**, and is not a good candidate for multi-process applications (like [Node.js – Cluster](http://nodejs.org/api/cluster.html) with many sub-processes). If your Node.js app is a single process app, you're good. If it is (or is going to be) a multi-process app, you're probably better off using [redis Pub/Sub](http://redis.io/topics/pubsub) or similar
23 |
24 | ## Key features
25 |
26 | * Dependency free
27 | * synchronisation decoupling
28 | * ES3 compatible. PubSubJS should be able to run everywhere that can execute JavaScript. Browsers, servers, ebook readers, old phones, game consoles.
29 | * AMD / CommonJS module support
30 | * No modification of subscribers (jQuery custom events modify subscribers)
31 | * Easy to understand and use (thanks to synchronisation decoupling)
32 | * Small(ish), less than 1kb minified and gzipped
33 |
34 | ## Getting PubSubJS
35 |
36 | There are several ways of getting PubSubJS
37 |
38 | * Install via npm (`npm install pubsub-js`)
39 | * Use it directly from a CDN
40 | - https://www.jsdelivr.com/package/npm/pubsub-js
41 | - https://cdnjs.com/libraries/pubsub-js
42 | - https://unpkg.com/pubsub-js
43 | * [Download a tagged version](https://github.com/mroderick/PubSubJS/tags) from GitHub
44 |
45 | **Note: the last version of this library available via bower is v1.5.4**
46 |
47 | ## Examples
48 |
49 | First you have to import the module:
50 |
51 | ```javascript
52 | import PubSub from 'pubsub-js'
53 |
54 | // or when using CommonJS
55 | const PubSub = require('pubsub-js');
56 | ```
57 |
58 | ### Basic example
59 |
60 | ```javascript
61 | // create a function to subscribe to topics
62 | var mySubscriber = function (msg, data) {
63 | console.log( msg, data );
64 | };
65 |
66 | // add the function to the list of subscribers for a particular topic
67 | // we're keeping the returned token, in order to be able to unsubscribe
68 | // from the topic later on
69 | var token = PubSub.subscribe('MY TOPIC', mySubscriber);
70 |
71 | // publish a topic asynchronously
72 | PubSub.publish('MY TOPIC', 'hello world!');
73 |
74 | // publish a topic synchronously, which is faster in some environments,
75 | // but will get confusing when one topic triggers new topics in the
76 | // same execution chain
77 | // USE WITH CAUTION, HERE BE DRAGONS!!!
78 | PubSub.publishSync('MY TOPIC', 'hello world!');
79 | ```
80 |
81 | ### Cancel specific subscription
82 |
83 | ```javascript
84 | // create a function to receive the topic
85 | var mySubscriber = function (msg, data) {
86 | console.log(msg, data);
87 | };
88 |
89 | // add the function to the list of subscribers to a particular topic
90 | // we're keeping the returned token, in order to be able to unsubscribe
91 | // from the topic later on
92 | var token = PubSub.subscribe('MY TOPIC', mySubscriber);
93 |
94 | // unsubscribe this subscriber from this topic
95 | PubSub.unsubscribe(token);
96 | ```
97 |
98 | ### Cancel all subscriptions for a function
99 |
100 | ```javascript
101 | // create a function to receive the topic
102 | var mySubscriber = function(msg, data) {
103 | console.log(msg, data);
104 | };
105 |
106 | // unsubscribe mySubscriber from ALL topics
107 | PubSub.unsubscribe(mySubscriber);
108 | ```
109 |
110 | ### Clear all subscriptions for a topic
111 |
112 | ```javascript
113 | PubSub.subscribe('a', myFunc1);
114 | PubSub.subscribe('a.b', myFunc2);
115 | PubSub.subscribe('a.b.c', myFunc3);
116 |
117 | PubSub.unsubscribe('a.b');
118 | // no further notifications for 'a.b' and 'a.b.c' topics
119 | // notifications for 'a' will still get published
120 | ```
121 |
122 | ### Clear all subscriptions
123 |
124 | ```javascript
125 | PubSub.clearAllSubscriptions();
126 | // all subscriptions are removed
127 | ```
128 |
129 | ### Get Subscriptions
130 |
131 | ```javascript
132 | PubSub.getSubscriptions('token');
133 | // subscriptions by token from all topics
134 | ```
135 |
136 | ### Count Subscriptions
137 |
138 | ```javascript
139 | PubSub.countSubscriptions('token');
140 | // count by token from all topics
141 | ```
142 |
143 |
144 | ### Error Handling
145 | ```javascript
146 | // isPublished is a boolean that represents if any subscribers was registered for this topic
147 | var isPublished = PubSub.publish('a');
148 |
149 | // token will be false if something went wrong and subscriber was not registered
150 | var token = PubSub.subscribe('MY TOPIC', mySubscriber);
151 | ```
152 |
153 | ### Hierarchical addressing
154 |
155 | ```javascript
156 | // create a subscriber to receive all topics from a hierarchy of topics
157 | var myToplevelSubscriber = function (msg, data) {
158 | console.log('top level: ', msg, data);
159 | }
160 |
161 | // subscribe to all topics in the 'car' hierarchy
162 | PubSub.subscribe('car', myToplevelSubscriber);
163 |
164 | // create a subscriber to receive only leaf topic from hierarchy op topics
165 | var mySpecificSubscriber = function (msg, data) {
166 | console.log('specific: ', msg, data);
167 | }
168 |
169 | // subscribe only to 'car.drive' topics
170 | PubSub.subscribe('car.drive', mySpecificSubscriber);
171 |
172 | // Publish some topics
173 | PubSub.publish('car.purchase', {name: 'my new car'});
174 | PubSub.publish('car.drive', {speed: '14'});
175 | PubSub.publish('car.sell', {newOwner: 'someone else'});
176 |
177 | // In this scenario, myToplevelSubscriber will be called for all
178 | // topics, three times in total
179 | // But, mySpecificSubscriber will only be called once, as it only
180 | // subscribes to the 'car.drive' topic
181 | ```
182 |
183 |
184 |
185 |
186 | ## Tips
187 |
188 | Use "constants" for topics and not string literals. PubSubJS uses strings as topics, and will happily try to deliver your topics with ANY topic. So, save yourself from frustrating debugging by letting the JavaScript engine complain
189 | when you make typos.
190 |
191 | ### Example of use of "constants"
192 |
193 | ```javascript
194 | // 👎 Bad usage
195 | PubSub.subscribe('hello', function (msg, data) {
196 | console.log(data)
197 | });
198 |
199 | PubSub.publish('hello', 'world');
200 |
201 | // 👍 Better usage
202 | var MY_TOPIC = 'hello';
203 | PubSub.subscribe(MY_TOPIC, function (msg, data) {
204 | console.log(data)
205 | });
206 |
207 | PubSub.publish(MY_TOPIC, 'world');
208 | ```
209 |
210 | ### Example of use of "symbol constants" with ES6/7 syntax
211 |
212 | ```javascript
213 | // event-types.js
214 | export const MY_TOPIC = Symbol('MY_TOPIC')
215 |
216 | // somefile.js
217 | import { MY_TOPIC } from './event-types.js'
218 | PubSub.subscribe(MY_TOPIC, function (msg, data) {
219 | console.log(data)
220 | });
221 |
222 | PubSub.publish(MY_TOPIC, 'world');
223 | ```
224 |
225 | ### Immediate Exceptions for stack traces in developer tools
226 |
227 | As of version 1.3.2, you can force immediate exceptions (instead of delayed exceptions), which has the benefit of maintaining the stack trace when viewed in dev tools.
228 |
229 | This should be considered a development only option, as PubSubJS was designed to try to deliver your topics to all subscribers, even when some fail.
230 |
231 | Setting immediate exceptions in development is easy, just tell PubSubJS about it after it has been loaded.
232 |
233 | ```javascript
234 | PubSub.immediateExceptions = true;
235 | ```
236 |
237 | ## Contributing to PubSubJS
238 |
239 | Please see [CONTRIBUTING.md](CONTRIBUTING.md)
240 |
241 |
242 | ## More about Publish/Subscribe
243 |
244 | * [The Many Faces of Publish/Subscribe](http://www.cs.ru.nl/~pieter/oss/manyfaces.pdf) (PDF)
245 | * [Addy Osmani's mini book on Patterns](http://addyosmani.com/resources/essentialjsdesignpatterns/book/#observerpatternjavascript)
246 | * [Publish / Subscribe Systems, A summary of 'The Many Faces of Publish / Subscribe'](http://downloads.ohohlfeld.com/talks/hohlfeld_schroeder-publish_subscribe_systems-dsmware_eurecom2007.pdf)
247 |
248 | ## Versioning
249 |
250 | PubSubJS uses [Semantic Versioning](http://semver.org/) for predictable versioning.
251 |
252 | ## Changelog
253 |
254 | Please see [https://github.com/mroderick/PubSubJS/releases](https://github.com/mroderick/PubSubJS/releases)
255 |
256 | ## License
257 |
258 | MIT: http://mrgnrdrck.mit-license.org
259 |
260 | ## Alternatives
261 |
262 | These are a few alternative projects that also implement topic based publish subscribe in JavaScript.
263 |
264 | * http://www.joezimjs.com/projects/publish-subscribe-jquery-plugin/
265 | * http://amplifyjs.com/api/pubsub/
266 | * http://radio.uxder.com/ — oriented towards 'channels', free of dependencies
267 | * https://github.com/pmelander/Subtopic - supports vanilla, underscore, jQuery and is even available in NuGet
268 |
--------------------------------------------------------------------------------
/src/pubsub.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2010,2011,2012,2013,2014 Morgan Roderick http://roderick.dk
3 | * License: MIT - http://mrgnrdrck.mit-license.org
4 | *
5 | * https://github.com/mroderick/PubSubJS
6 | */
7 |
8 | (function (root, factory){
9 | 'use strict';
10 |
11 | var PubSub = {};
12 |
13 | if (root.PubSub) {
14 | PubSub = root.PubSub;
15 | console.warn("PubSub already loaded, using existing version");
16 | } else {
17 | root.PubSub = PubSub;
18 | factory(PubSub);
19 | }
20 | // CommonJS and Node.js module support
21 | if (typeof exports === 'object'){
22 | if (module !== undefined && module.exports) {
23 | exports = module.exports = PubSub; // Node.js specific `module.exports`
24 | }
25 | exports.PubSub = PubSub; // CommonJS module 1.1.1 spec
26 | module.exports = exports = PubSub; // CommonJS
27 | }
28 | // AMD support
29 | /* eslint-disable no-undef */
30 | else if (typeof define === 'function' && define.amd){
31 | define(function() { return PubSub; });
32 | /* eslint-enable no-undef */
33 | }
34 |
35 | }(( typeof window === 'object' && window ) || this || global, function (PubSub){
36 | 'use strict';
37 |
38 | var messages = {},
39 | lastUid = -1,
40 | ALL_SUBSCRIBING_MSG = '*';
41 |
42 | function hasKeys(obj){
43 | var key;
44 |
45 | for (key in obj){
46 | if ( Object.prototype.hasOwnProperty.call(obj, key) ){
47 | return true;
48 | }
49 | }
50 | return false;
51 | }
52 |
53 | /**
54 | * Returns a function that throws the passed exception, for use as argument for setTimeout
55 | * @alias throwException
56 | * @function
57 | * @param { Object } ex An Error object
58 | */
59 | function throwException( ex ){
60 | return function reThrowException(){
61 | throw ex;
62 | };
63 | }
64 |
65 | function callSubscriberWithDelayedExceptions( subscriber, message, data ){
66 | try {
67 | subscriber( message, data );
68 | } catch( ex ){
69 | setTimeout( throwException( ex ), 0);
70 | }
71 | }
72 |
73 | function callSubscriberWithImmediateExceptions( subscriber, message, data ){
74 | subscriber( message, data );
75 | }
76 |
77 | function deliverMessage( originalMessage, matchedMessage, data, immediateExceptions ){
78 | var subscribers = messages[matchedMessage],
79 | callSubscriber = immediateExceptions ? callSubscriberWithImmediateExceptions : callSubscriberWithDelayedExceptions,
80 | s;
81 |
82 | if ( !Object.prototype.hasOwnProperty.call( messages, matchedMessage ) ) {
83 | return;
84 | }
85 |
86 | for (s in subscribers){
87 | if ( Object.prototype.hasOwnProperty.call(subscribers, s)){
88 | callSubscriber( subscribers[s], originalMessage, data );
89 | }
90 | }
91 | }
92 |
93 | function createDeliveryFunction( message, data, immediateExceptions ){
94 | return function deliverNamespaced(){
95 | var topic = String( message ),
96 | position = topic.lastIndexOf( '.' );
97 |
98 | // deliver the message as it is now
99 | deliverMessage(message, message, data, immediateExceptions);
100 |
101 | // trim the hierarchy and deliver message to each level
102 | while( position !== -1 ){
103 | topic = topic.substr( 0, position );
104 | position = topic.lastIndexOf('.');
105 | deliverMessage( message, topic, data, immediateExceptions );
106 | }
107 |
108 | deliverMessage(message, ALL_SUBSCRIBING_MSG, data, immediateExceptions);
109 | };
110 | }
111 |
112 | function hasDirectSubscribersFor( message ) {
113 | var topic = String( message ),
114 | found = Boolean(Object.prototype.hasOwnProperty.call( messages, topic ) && hasKeys(messages[topic]));
115 |
116 | return found;
117 | }
118 |
119 | function messageHasSubscribers( message ){
120 | var topic = String( message ),
121 | found = hasDirectSubscribersFor(topic) || hasDirectSubscribersFor(ALL_SUBSCRIBING_MSG),
122 | position = topic.lastIndexOf( '.' );
123 |
124 | while ( !found && position !== -1 ){
125 | topic = topic.substr( 0, position );
126 | position = topic.lastIndexOf( '.' );
127 | found = hasDirectSubscribersFor(topic);
128 | }
129 |
130 | return found;
131 | }
132 |
133 | function publish( message, data, sync, immediateExceptions ){
134 | message = (typeof message === 'symbol') ? message.toString() : message;
135 |
136 | var deliver = createDeliveryFunction( message, data, immediateExceptions ),
137 | hasSubscribers = messageHasSubscribers( message );
138 |
139 | if ( !hasSubscribers ){
140 | return false;
141 | }
142 |
143 | if ( sync === true ){
144 | deliver();
145 | } else {
146 | setTimeout( deliver, 0 );
147 | }
148 | return true;
149 | }
150 |
151 | /**
152 | * Publishes the message, passing the data to it's subscribers
153 | * @function
154 | * @alias publish
155 | * @param { String } message The message to publish
156 | * @param {} data The data to pass to subscribers
157 | * @return { Boolean }
158 | */
159 | PubSub.publish = function( message, data ){
160 | return publish( message, data, false, PubSub.immediateExceptions );
161 | };
162 |
163 | /**
164 | * Publishes the message synchronously, passing the data to it's subscribers
165 | * @function
166 | * @alias publishSync
167 | * @param { String } message The message to publish
168 | * @param {} data The data to pass to subscribers
169 | * @return { Boolean }
170 | */
171 | PubSub.publishSync = function( message, data ){
172 | return publish( message, data, true, PubSub.immediateExceptions );
173 | };
174 |
175 | /**
176 | * Subscribes the passed function to the passed message. Every returned token is unique and should be stored if you need to unsubscribe
177 | * @function
178 | * @alias subscribe
179 | * @param { String } message The message to subscribe to
180 | * @param { Function } func The function to call when a new message is published
181 | * @return { String }
182 | */
183 | PubSub.subscribe = function( message, func ){
184 | if ( typeof func !== 'function'){
185 | return false;
186 | }
187 |
188 | message = (typeof message === 'symbol') ? message.toString() : message;
189 |
190 | // message is not registered yet
191 | if ( !Object.prototype.hasOwnProperty.call( messages, message ) ){
192 | messages[message] = {};
193 | }
194 |
195 | // forcing token as String, to allow for future expansions without breaking usage
196 | // and allow for easy use as key names for the 'messages' object
197 | var token = 'uid_' + String(++lastUid);
198 | messages[message][token] = func;
199 |
200 | // return token for unsubscribing
201 | return token;
202 | };
203 |
204 | PubSub.subscribeAll = function( func ){
205 | return PubSub.subscribe(ALL_SUBSCRIBING_MSG, func);
206 | };
207 |
208 | /**
209 | * Subscribes the passed function to the passed message once
210 | * @function
211 | * @alias subscribeOnce
212 | * @param { String } message The message to subscribe to
213 | * @param { Function } func The function to call when a new message is published
214 | * @return { PubSub }
215 | */
216 | PubSub.subscribeOnce = function( message, func ){
217 | var token = PubSub.subscribe( message, function(){
218 | // before func apply, unsubscribe message
219 | PubSub.unsubscribe( token );
220 | func.apply( this, arguments );
221 | });
222 | return PubSub;
223 | };
224 |
225 | /**
226 | * Clears all subscriptions
227 | * @function
228 | * @public
229 | * @alias clearAllSubscriptions
230 | */
231 | PubSub.clearAllSubscriptions = function clearAllSubscriptions(){
232 | messages = {};
233 | };
234 |
235 | /**
236 | * Clear subscriptions by the topic
237 | * @function
238 | * @public
239 | * @alias clearAllSubscriptions
240 | * @return { int }
241 | */
242 | PubSub.clearSubscriptions = function clearSubscriptions(topic){
243 | var m;
244 | for (m in messages){
245 | if (Object.prototype.hasOwnProperty.call(messages, m) && m.indexOf(topic) === 0){
246 | delete messages[m];
247 | }
248 | }
249 | };
250 |
251 | /**
252 | Count subscriptions by the topic
253 | * @function
254 | * @public
255 | * @alias countSubscriptions
256 | * @return { Array }
257 | */
258 | PubSub.countSubscriptions = function countSubscriptions(topic){
259 | var m;
260 | // eslint-disable-next-line no-unused-vars
261 | var token;
262 | var count = 0;
263 | for (m in messages) {
264 | if (Object.prototype.hasOwnProperty.call(messages, m) && m.indexOf(topic) === 0) {
265 | for (token in messages[m]) {
266 | count++;
267 | }
268 | break;
269 | }
270 | }
271 | return count;
272 | };
273 |
274 |
275 | /**
276 | Gets subscriptions by the topic
277 | * @function
278 | * @public
279 | * @alias getSubscriptions
280 | */
281 | PubSub.getSubscriptions = function getSubscriptions(topic){
282 | var m;
283 | var list = [];
284 | for (m in messages){
285 | if (Object.prototype.hasOwnProperty.call(messages, m) && m.indexOf(topic) === 0){
286 | list.push(m);
287 | }
288 | }
289 | return list;
290 | };
291 |
292 | /**
293 | * Removes subscriptions
294 | *
295 | * - When passed a token, removes a specific subscription.
296 | *
297 | * - When passed a function, removes all subscriptions for that function
298 | *
299 | * - When passed a topic, removes all subscriptions for that topic (hierarchy)
300 | * @function
301 | * @public
302 | * @alias subscribeOnce
303 | * @param { String | Function } value A token, function or topic to unsubscribe from
304 | * @example // Unsubscribing with a token
305 | * var token = PubSub.subscribe('mytopic', myFunc);
306 | * PubSub.unsubscribe(token);
307 | * @example // Unsubscribing with a function
308 | * PubSub.unsubscribe(myFunc);
309 | * @example // Unsubscribing from a topic
310 | * PubSub.unsubscribe('mytopic');
311 | */
312 | PubSub.unsubscribe = function(value){
313 | var descendantTopicExists = function(topic) {
314 | var m;
315 | for ( m in messages ){
316 | if ( Object.prototype.hasOwnProperty.call(messages, m) && m.indexOf(topic) === 0 ){
317 | // a descendant of the topic exists:
318 | return true;
319 | }
320 | }
321 |
322 | return false;
323 | },
324 | isTopic = typeof value === 'string' && ( Object.prototype.hasOwnProperty.call(messages, value) || descendantTopicExists(value) ),
325 | isToken = !isTopic && typeof value === 'string',
326 | isFunction = typeof value === 'function',
327 | result = false,
328 | m, message, t;
329 |
330 | if (isTopic){
331 | PubSub.clearSubscriptions(value);
332 | return;
333 | }
334 |
335 | for ( m in messages ){
336 | if ( Object.prototype.hasOwnProperty.call( messages, m ) ){
337 | message = messages[m];
338 |
339 | if ( isToken && message[value] ){
340 | delete message[value];
341 | result = value;
342 | // tokens are unique, so we can just stop here
343 | break;
344 | }
345 |
346 | if (isFunction) {
347 | for ( t in message ){
348 | if (Object.prototype.hasOwnProperty.call(message, t) && message[t] === value){
349 | delete message[t];
350 | result = true;
351 | }
352 | }
353 | }
354 | }
355 | }
356 |
357 | return result;
358 | };
359 | }));
360 |
--------------------------------------------------------------------------------