├── .nvmrc ├── .gitignore ├── src ├── index.js ├── Buzy.test.js ├── utils.js └── Buzy.js ├── package.json ├── LICENSE └── README.md /.nvmrc: -------------------------------------------------------------------------------- 1 | v8.0.0 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Aditya Subramanyam on 07/09/17. 3 | */ 4 | module.exports = require('./Buzy'); 5 | 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "buzy", 3 | "version": "0.1.5", 4 | "description": "Async queue manager for node and browser", 5 | "main": "src/index.js", 6 | "repository": "git@github.com:subbu963/buzy.git", 7 | "author": "subramanyam963@gmail.com", 8 | "license": "MIT", 9 | "private": false 10 | } 11 | -------------------------------------------------------------------------------- /src/Buzy.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by adithya.s on 23/09/17. 3 | */ 4 | const Buzy = require('./Buzy'); 5 | const b = new Buzy([function (message) { 6 | console.log('in b', message); 7 | }]); 8 | const c = new Buzy([function (message) { 9 | console.log('in c', message, c.isBusy()) 10 | }], [b]); 11 | 12 | b.addPromise(new Promise(function (resolve, reject) { 13 | setTimeout(function () { 14 | resolve('yo1'); 15 | }, 1000); 16 | })); 17 | 18 | setTimeout(function () { 19 | b.addPromise(new Promise(function (resolve, reject) { 20 | setTimeout(function () { 21 | reject('yo2'); 22 | }, 500); 23 | })); 24 | }, 100); 25 | setTimeout(function () { 26 | b.addPromise(new Promise(function (resolve, reject) { 27 | setTimeout(function () { 28 | reject('yo3'); 29 | }, 10); 30 | })); 31 | }, 500); -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Aditya Subramanyam 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by adithya.s on 23/09/17. 3 | */ 4 | function noop() {} 5 | function type(value) { 6 | const type = Object.prototype.toString.call(value).match(/\w+/g)[1]; 7 | if(type === 'Object') { 8 | return value && value.constructor ? value.constructor.name : type; 9 | } 10 | return type; 11 | } 12 | function defer(fn, args) { 13 | if(!isFunction(fn)) { 14 | throw new Error(`callback should be a function, ${type(fn)} provided`); 15 | } 16 | args = isArray(args) ? args : [args]; 17 | setTimeout(fn, 0, ...args); 18 | } 19 | function combinePromises(promises) { 20 | if(!isArray(promises)) { 21 | throw new Error(`callback should be an array, ${type(promises)} provided`); 22 | } 23 | Promise.all(promises.map(promise => { 24 | if(!isPromise(promise)) { 25 | throw new Error(`promise expected, ${type(promise)} provided`); 26 | } 27 | return promise.then(function(value) { 28 | return { 29 | value: value, 30 | status: 'resolved' 31 | }; 32 | }, function(error) { 33 | return { 34 | error: error, 35 | status: 'rejected' 36 | }; 37 | }) 38 | })) 39 | } 40 | function isFunction(fn) { 41 | return type(fn) === 'Function'; 42 | } 43 | function isArray(ar) { 44 | return type(ar) === 'Array'; 45 | } 46 | function isPromise(promise) { 47 | return promise && isFunction(promise.then); 48 | } 49 | module.exports = { 50 | defer, 51 | noop, 52 | type, 53 | combinePromises, 54 | isFunction, 55 | isArray, 56 | isPromise 57 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # buzy 2 | Promise based async queue manager for node and browser. 3 | 4 | Buzy is a blackbox into which you push promises and at each point in time you can know if any of the promises are still in pending state(busy state). Optionally you can subscribe for the event bus which will let you know when there is change in the blackbox state 5 | 6 | Its particularly useful where you want to know if your system is busy with async activities like ajax calls etc. You can push promises in to the queue and buzy will do the job of letting you know when the state of the system changes. 7 | Use cases can be: 8 | 1) Show loaders on ajax calls 9 | 2) Check if any of your tasks are pending before closing the browser 10 | and similar others 11 | 12 | ## Installation 13 | ```bash 14 | $ npm install buzy 15 | ``` 16 | 17 | ## Syntax 18 | ```javascript 19 | new Buzy(subscribers, buzies); 20 | ``` 21 | - subscribers - array of functions which will be called upon the change when there is a state change or when a promise is resolved/reject(this wont be triggered for buzies dependent on other buzies) 22 | - buzies - array of buzies to subscribe on to 23 | 24 | Subscribers will be called with the following message data structure 25 | ```javascript 26 | { 27 | code: number, // 0 - STATE change, 1 - RESOLVE, 2 - REJECT 28 | busy: true/false, 29 | promise: promise, //promise which is last resolved/rejected 30 | value: value, // value of the resolution 31 | error: error // error of the rejection 32 | } 33 | ``` 34 | 35 | New promises can be added with: 36 | ```javascript 37 | buzy.addPromise(promise) //single promise 38 | buzy.addPromises(promises) //array of promises 39 | ``` 40 | New subscribers can be added with: 41 | ```javascript 42 | buzy.addSubscriber(subscriber) //single subscriber 43 | buzy.addSubscribers(subscribers) //array of subscribers 44 | ``` 45 | New buzies can be added with: 46 | ```javascript 47 | buzy.addBuzy(buzy) //single buzy 48 | buzy.addBuzies(buzies)//array of buzies 49 | ``` 50 | 51 | To check if buzy is busy or not: 52 | ```javascript 53 | buzy.isBusy() //true or false 54 | ``` 55 | 56 | ## Examples 57 | ```javascript 58 | import Buzy from 'buzy'; 59 | 60 | const buzy1 = new Buzy([function(message) { 61 | console.log(message); 62 | /* 63 | Prints the following: 64 | { 65 | code: 0, 66 | busy: true 67 | } 68 | And after 1000 milliseconds 69 | { 70 | code: 1, 71 | promise: Promise{'done'}, 72 | value: 'done' 73 | } 74 | And then 75 | { 76 | code: 0, 77 | busy: false 78 | } 79 | */ 80 | }]); 81 | const buzy2 = new Buzy([function(message) { 82 | console.log(message); 83 | /* 84 | Prints the following: 85 | { 86 | code: 0, 87 | busy: true 88 | } 89 | And after fetch is complete 90 | { 91 | code: 1 or 2 based on fetch result, 92 | promise: Promise{res from fetch}, 93 | value: value or error: error based fetch result 94 | } 95 | And then after buzy1 is done with all the tasks 96 | { 97 | code: 0, 98 | busy: false 99 | } 100 | */ 101 | }], [buzy1]); 102 | 103 | buzy1.addPromise(new Promise(function(resolve, reject) { 104 | setTimeout(function() { 105 | resolve('done'); 106 | }, 1000); 107 | })) 108 | buzy2.addPromise(fetch('http://someurl')); 109 | ``` 110 | Check [Buzy.test.js](src/Buzy.test.js) for more exmaples 111 | ## To do 112 | 1) Cancellation of requests 113 | 2) Removal of subscribers/buzies 114 | 3) You tell me! -------------------------------------------------------------------------------- /src/Buzy.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by adithya.s on 23/09/17. 3 | */ 4 | const utils = require('./utils'); 5 | 6 | const stateMap = new WeakMap(); 7 | const MESSAGE_CODE_MAP = { 8 | STATE: 0, 9 | RESOLVE: 1, 10 | REJECT: 2 11 | }; 12 | function asyncEvokeFunctions(arr, ...args) { 13 | arr.forEach(function(fn) { 14 | utils.defer(fn, ...args); 15 | }); 16 | } 17 | function isBuzy(buzy) { 18 | return buzy instanceof Buzy; 19 | } 20 | 21 | class Buzy { 22 | constructor(subscribers, buzies) { 23 | stateMap.set(this, { 24 | busy: false, 25 | pendingPromises: [], 26 | subscribers: [], 27 | buzies: [] 28 | }); 29 | this.addSubscribers(subscribers); 30 | this.addBuzies(buzies); 31 | } 32 | isBusy() { 33 | const state = stateMap.get(this); 34 | return state.busy; 35 | } 36 | addPromises(promises) { 37 | if(!utils.isArray(promises)) { 38 | throw new Error(`array expected, ${utils.type(promises)} provided`); 39 | } 40 | promises.forEach(this.addPromise.bind(this)); 41 | } 42 | addPromise(promise) { 43 | if(!utils.isPromise(promise)) { 44 | throw new Error(`promise expected, ${utils.type(promise)} provided`); 45 | } 46 | const state = stateMap.get(this); 47 | state.pendingPromises.push(promise); 48 | if(!state.busy) { 49 | asyncEvokeFunctions(state.subscribers, { 50 | code: MESSAGE_CODE_MAP.STATE, 51 | busy: true 52 | }); 53 | } 54 | state.busy = true; 55 | promise.then(res => { 56 | asyncEvokeFunctions(state.subscribers, { 57 | code: MESSAGE_CODE_MAP.RESOLVE, 58 | value: res, 59 | promise: promise 60 | }); 61 | }, error => { 62 | asyncEvokeFunctions(state.subscribers, { 63 | code: MESSAGE_CODE_MAP.REJECT, 64 | error: error, 65 | promise: promise 66 | }); 67 | }).then(res => { 68 | const idx = state.pendingPromises.findIndex(item => item === promise); 69 | state.pendingPromises.splice(idx, 1); 70 | if(!state.pendingPromises.length) { 71 | state.busy = false; 72 | asyncEvokeFunctions(state.subscribers, { 73 | code: MESSAGE_CODE_MAP.STATE, 74 | busy: state.busy 75 | }); 76 | } 77 | }); 78 | } 79 | addSubscriber(subscriber) { 80 | if(!utils.isFunction(subscriber)) { 81 | throw new Error(`subscriber should be a function, ${utils.type(subscriber)} provided`); 82 | } 83 | const state = stateMap.get(this); 84 | state.subscribers.push(subscriber); 85 | } 86 | addSubscribers(subscribers) { 87 | if(!subscribers) { 88 | return; 89 | } 90 | if(!utils.isArray(subscribers)) { 91 | throw new Error(`array expected, ${utils.type(subscribers)} provided`); 92 | } 93 | subscribers.forEach(this.addSubscriber.bind(this)); 94 | } 95 | addBuzy(buzy) { 96 | if(!isBuzy(buzy)) { 97 | throw new Error(`instance of Buzy expected, ${utils.type(buzy)} provided`); 98 | } 99 | const state = stateMap.get(this); 100 | state.buzies.push(buzy); 101 | if(buzy.isBusy() && !state.busy) { 102 | asyncEvokeFunctions(state.subscribers, { 103 | code: MESSAGE_CODE_MAP.STATE, 104 | busy: state.busy 105 | }); 106 | } 107 | state.busy = state.busy || buzy.isBusy(); 108 | buzy.addSubscriber(function(message) { 109 | if(MESSAGE_CODE_MAP.STATE !== message.code) { 110 | return; 111 | } 112 | const isBusy = !!Math.max(...state.buzies.map(_buzy => _buzy.isBusy())); 113 | if(state.busy !== isBusy) { 114 | asyncEvokeFunctions(state.subscribers, { 115 | code: MESSAGE_CODE_MAP.STATE, 116 | busy: isBusy 117 | }); 118 | } 119 | state.busy = isBusy; 120 | }); 121 | } 122 | addBuzies(buzies) { 123 | if(!buzies) { 124 | return; 125 | } 126 | if(!utils.isArray(buzies)) { 127 | throw new Error(`array expected, ${utils.type(buzies)} provided`); 128 | } 129 | buzies.forEach(this.addBuzy.bind(this)); 130 | } 131 | } 132 | module.exports = Buzy; --------------------------------------------------------------------------------