├── 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 | iframe demo frame 1 6 | 7 | 23 | 24 | 25 |

frame1.html

26 | 27 | 28 | -------------------------------------------------------------------------------- /iframe/frame2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | iframe demo frame 2 6 | 7 | 23 | 24 | 25 |

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 | iframe demo 6 | 7 | 10 | 11 | 12 |

Cross iframe communication with PubSubJS

13 |

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 |
8 |
throwException(ex)
9 |

Returns a function that throws the passed exception, for use as argument for setTimeout

10 |
11 |
publish(message, data)Boolean
12 |

Publishes the message, passing the data to it's subscribers

13 |
14 |
publishSync(message, data)Boolean
15 |

Publishes the message synchronously, passing the data to it's subscribers

16 |
17 |
subscribe(message, func)String
18 |

Subscribes the passed function to the passed message. Every returned token is unique and should be stored if you need to unsubscribe

19 |
20 |
subscribeOnce(message, func)PubSub
21 |

Subscribes the passed function to the passed message once

22 |
23 |
clearAllSubscriptions()
24 |

Clears all subscriptions

25 |
26 |
clearAllSubscriptions()int
27 |

Clear subscriptions by the topic

28 |
29 |
countSubscriptions()Array
30 |

Count subscriptions by the topic

31 |
32 |
getSubscriptions()
33 |

Gets subscriptions by the topic

34 |
35 |
subscribeOnce(value)
36 |

Removes subscriptions

37 | 45 |
46 |
47 | 48 | 49 | 50 | ## throwException(ex) 51 | Returns a function that throws the passed exception, for use as argument for setTimeout 52 | 53 | **Kind**: global function 54 | 55 | | Param | Type | Description | 56 | | --- | --- | --- | 57 | | ex | 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 |
2 |

3 | PubSubJS 4 |

5 | 6 |

7 | NPM versionMIT LicenseNPM downloads/monthCoverage Status 11 |

12 |
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 | --------------------------------------------------------------------------------