├── test ├── .gitignore ├── tsconfig.json └── test.ts ├── .gitignore ├── .travis.yml ├── .npmignore ├── rollup.config.js ├── src ├── index.ts ├── tsconfig.json ├── Task.ts ├── tslint.json └── TaskQueue.ts ├── LICENSE ├── package.json └── README.md /test/.gitignore: -------------------------------------------------------------------------------- 1 | *.js 2 | *.d.ts 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | package-lock.json 4 | *.lock 5 | *.log.* 6 | *.log 7 | *.tgz 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "10" 5 | - "8" 6 | - "6" 7 | - "4" 8 | - "0.12" 9 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | test/ 3 | src/ 4 | package-lock.json 5 | .travis.yml 6 | *.lock 7 | *.log.* 8 | *.log 9 | *.tgz 10 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | module.exports = require('autoroll')( 2 | require('./package.json'), 3 | { 4 | include: [ 5 | 'cdata' 6 | ] 7 | } 8 | ); 9 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | // This file is part of cwait, copyright (c) 2015- BusFaster Ltd. 2 | // Released under the MIT license, see LICENSE. 3 | 4 | export { Task, PromisyClass } from './Task'; 5 | export { TaskQueue } from './TaskQueue'; 6 | -------------------------------------------------------------------------------- /test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": false, 4 | "lib": ["es5", "es2015.collection"], 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "noImplicitAny": true, 8 | "noImplicitThis": true, 9 | "removeComments": false, 10 | "strictNullChecks": true, 11 | "target": "es5" 12 | }, 13 | "files": [ 14 | "test.ts" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": true, 3 | "compilerOptions": { 4 | "declaration": true, 5 | "declarationDir": "../dist/esm", 6 | "lib": [ "es5", "es2015.collection" ], 7 | "module": "commonjs", 8 | "moduleResolution": "node", 9 | "noEmitHelpers": true, 10 | "noImplicitAny": true, 11 | "noImplicitThis": true, 12 | "outDir": "../dist/cjs", 13 | "removeComments": false, 14 | "sourceMap": false, 15 | "strictFunctionTypes": true, 16 | "strictNullChecks": true, 17 | "target": "es5", 18 | "types": [ "node" ] 19 | }, 20 | "files": [ 21 | "index.ts" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-2017 BusFaster Ltd 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 | 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cwait", 3 | "version": "1.1.2", 4 | "description": "Limit number of promises running in parallel", 5 | "main": "dist/cjs/index.js", 6 | "module": "dist/esm/index.js", 7 | "browser": "dist/umd/index.js", 8 | "typings": "dist/esm/index.d.ts", 9 | "scripts": { 10 | "tsc": "tsc", 11 | "rollup": "rollup", 12 | "prepublish": "(checkver ge 5.0.0 && tsc -m es6 --outdir dist/esm -p src && rollup -c) || tsc -p src", 13 | "test": "tsc -p test && node test/test.js" 14 | }, 15 | "author": "Juha Järvi", 16 | "license": "MIT", 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/charto/cwait.git" 20 | }, 21 | "bugs": { 22 | "url": "https://github.com/charto/cwait/issues" 23 | }, 24 | "homepage": "https://github.com/charto/cwait#readme", 25 | "keywords": [ 26 | "promise", 27 | "concurrent", 28 | "concurrency", 29 | "parallel", 30 | "timeout", 31 | "delay", 32 | "wait", 33 | "queue" 34 | ], 35 | "devDependencies": { 36 | "@types/bluebird": "^3.5.24", 37 | "@types/node": "^10.12.1", 38 | "autoroll": "0.1.0", 39 | "bluebird": "^3.5.2", 40 | "rollup": "^0.66.6", 41 | "typescript": "^3.1.4" 42 | }, 43 | "dependencies": { 44 | "cdata": "^0.1.1" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Task.ts: -------------------------------------------------------------------------------- 1 | // This file is part of cwait, copyright (c) 2015- BusFaster Ltd. 2 | // Released under the MIT license, see LICENSE. 3 | 4 | /** Promise class shim with basic functionality. @ignore internal use. */ 5 | 6 | export interface PromisyClass { 7 | 8 | new(handler: (resolve: (result?: Result) => any, reject: (err?: any) => any) => any): PromiseLike; 9 | 10 | resolve(result: Result | PromiseLike): PromiseLike; 11 | reject(err: any): PromiseLike; 12 | 13 | } 14 | 15 | /** Call func and return a promise for its result. 16 | * Optionally call given resolve or reject handler when the promise settles. */ 17 | 18 | export function tryFinally( 19 | func: () => Result | PromiseLike, 20 | onFinish: () => void, 21 | Promise: PromisyClass, 22 | resolve?: (result: Result) => void, 23 | reject?: (err: any) => void 24 | ) { 25 | let promise: PromiseLike; 26 | 27 | try { 28 | promise = Promise.resolve(func()); 29 | } catch(err) { 30 | // If func threw an error, return a rejected promise. 31 | promise = Promise.reject(err); 32 | } 33 | 34 | promise.then(onFinish, onFinish); 35 | if(resolve) promise.then(resolve, reject); 36 | 37 | return(promise); 38 | } 39 | 40 | /** Task wraps a promise, delaying it until some resource gets less busy. */ 41 | 42 | export class Task { 43 | 44 | constructor( 45 | private func: () => Result | PromiseLike, 46 | private Promise: PromisyClass, 47 | public stamp: number 48 | ) {} 49 | 50 | /** Wrap task result in a new promise so it can be resolved later. */ 51 | 52 | delay() { 53 | if(!this.promise) { 54 | this.promise = new this.Promise((resolve: any, reject: any) => { 55 | this.resolve = resolve; 56 | this.reject = reject; 57 | }); 58 | } 59 | 60 | return(this.promise); 61 | } 62 | 63 | /** Start the task and call onFinish when done. */ 64 | 65 | resume(onFinish: () => void) { 66 | return(tryFinally(this.func, onFinish, this.Promise, this.resolve, this.reject)); 67 | } 68 | 69 | private promise: PromiseLike; 70 | private resolve: (result: Result) => void; 71 | private reject: (err: any) => void; 72 | 73 | } 74 | -------------------------------------------------------------------------------- /src/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "align": true, 4 | "ban": false, 5 | "class-name": true, 6 | "comment-format": [ true, "check-space" ], 7 | "curly": false, 8 | "eofline": true, 9 | "forin": true, 10 | "indent": [ true, "tabs" ], 11 | "interface-name": [ true, "never-prefix" ], 12 | "jsdoc-format": false, 13 | "label-position": true, 14 | "max-line-length": [ true, 96 ], 15 | "member-access": false, 16 | "member-ordering": [ true, { "order": [ 17 | "constructor", 18 | 19 | "public-static-method", 20 | "private-static-method", 21 | 22 | "public-instance-method", 23 | "private-instance-method", 24 | 25 | "private-static-field", 26 | "public-static-field", 27 | 28 | "private-instance-field", 29 | "public-instance-field" 30 | ] } ], 31 | "new-parens": true, 32 | "no-angle-bracket-type-assertion": true, 33 | "no-any": false, 34 | "no-arg": true, 35 | "no-bitwise": false, 36 | "no-conditional-assignment": true, 37 | "no-consecutive-blank-lines": true, 38 | "no-console": [ true, "log", "warn", "error" ], 39 | "no-construct": true, 40 | "no-debugger": true, 41 | "no-default-export": true, 42 | "no-duplicate-variable": true, 43 | "no-empty": true, 44 | "no-eval": true, 45 | "no-inferrable-types": true, 46 | "no-internal-module": true, 47 | "no-invalid-this": true, 48 | "no-namespace": false, 49 | "no-null-keyword": false, 50 | "no-reference": true, 51 | "no-require-imports": true, 52 | "no-shadowed-variable": true, 53 | "no-string-literal": false, 54 | "no-switch-case-fall-through": true, 55 | "no-trailing-whitespace": true, 56 | "no-unused-expression": true, 57 | "no-use-before-declare": true, 58 | "no-var-keyword": true, 59 | "no-var-requires": true, 60 | "object-literal-sort-keys": true, 61 | "one-line": [ true, "check-catch", "check-else", "check-finally", "check-open-brace" ], 62 | "one-variable-per-declaration": true, 63 | "quotemark": [ true, "single" ], 64 | "radix": "true", 65 | "semicolon": [ true, "always" ], 66 | "switch-default": true, 67 | "trailing-comma": [ true, { 68 | "multiline": "never", 69 | "singleline": "never" 70 | }], 71 | "triple-equals": false, 72 | "typedef": [ true, "arrow-parameter", "property-declaration" ], 73 | "typedef-whitespace": [ true, 74 | { "call-signature": "nospace", "index-signature": "nospace", "parameter": "nospace", "property-declaration": "nospace", "variable-declaration": "nospace" }, 75 | { "call-signature": "onespace", "index-signature": "onespace", "parameter": "onespace", "property-declaration": "onespace", "variable-declaration": "onespace" } 76 | ], 77 | "use-isnan": true, 78 | "variable-name": [ true, "check-format", "allow-leading-underscore", "allow-pascal-case" ], 79 | "whitespace": [ true, "check-decl", "check-operator", "check-module", "check-separator", "check-type", "check-typecast" ] 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /test/test.ts: -------------------------------------------------------------------------------- 1 | // This file is part of cwait, copyright (c) 2015-2016 BusFaster Ltd. 2 | // Released under the MIT license, see LICENSE. 3 | 4 | declare var process: any; 5 | 6 | import * as Promise from 'bluebird'; 7 | import {TaskQueue} from '..'; 8 | 9 | function equals(a: any, b: any) { 10 | if(a != b) { 11 | console.log('ERROR ' + a + ' != ' + b); 12 | process.exit(1); 13 | } 14 | 15 | console.log('OK ' + a); 16 | } 17 | 18 | var testCount = 0; 19 | 20 | function test1() { 21 | /** Queue allowing 3 concurrent function calls. */ 22 | var queue = new TaskQueue(Promise, 3); 23 | 24 | var running = 0; 25 | var maxRunning = 0; 26 | 27 | /** Test function returning a promise with a slight delay 28 | * and tracking concurrent executions. */ 29 | 30 | function run(item: string) { 31 | if(++running > maxRunning) maxRunning = running; 32 | 33 | return(Promise.delay(100, true).then(() => { 34 | --running; 35 | return(item); 36 | })); 37 | } 38 | 39 | /** List of 6 items to loop through. */ 40 | var list = '123456'.split(''); 41 | 42 | // Run test without limiting concurrency. 43 | 44 | return( 45 | Promise.map(list, run).then((result: string[]) => { 46 | ++testCount; 47 | 48 | equals(result.join(''), '123456'); 49 | equals(maxRunning, 6); 50 | 51 | maxRunning = 0; 52 | 53 | // Run test and limit concurrency. 54 | 55 | return(Promise.map(list, queue.wrap(run))) 56 | }).then((result: string[]) => { 57 | ++testCount; 58 | 59 | equals(result.join(''), '123456'); 60 | equals(maxRunning, 3); 61 | }) 62 | ); 63 | } 64 | 65 | function test2() { 66 | function throws() { 67 | if(1) throw(new Error('Beep')); 68 | 69 | return(Promise.resolve(true)); 70 | } 71 | 72 | var queue = new TaskQueue(Promise, 1); 73 | 74 | return( 75 | Promise.all([ 76 | queue.wrap(throws)().then(null as any, (err: any) => ++testCount), 77 | queue.wrap(throws)().then(null as any, (err: any) => ++testCount) 78 | ]) 79 | ); 80 | } 81 | 82 | function test3() { 83 | function rejects() { 84 | return(Promise.reject(new Error('Beep'))); 85 | } 86 | 87 | var queue = new TaskQueue(Promise, 1); 88 | 89 | return( 90 | Promise.all([ 91 | queue.wrap(rejects)().then(null as any, (err: any) => ++testCount), 92 | queue.wrap(rejects)().then(null as any, (err: any) => ++testCount) 93 | ]) 94 | ); 95 | } 96 | 97 | function test4() { 98 | const unsorted: number[] = []; 99 | 100 | function compare(a: number, b: number) { return(a - b); } 101 | 102 | for(let num = 0; num < 100; ++num) { 103 | unsorted[num] = ~~(Math.random() * 50); 104 | } 105 | 106 | const correct = unsorted.slice(0).sort(compare); 107 | 108 | const queue = new TaskQueue(Promise, 1); 109 | const result: number[] = []; 110 | const start = new Date().getTime(); 111 | 112 | return( 113 | Promise.map( 114 | unsorted, 115 | (item: number) => queue.add( 116 | () => { 117 | const delta = new Date().getTime() - start - item * 10; 118 | if(delta > 0 && delta < 20) result.push(item); 119 | }, 120 | item * 10 121 | ) 122 | ).then( 123 | () => result.join(' ') == correct.join(' ') && ++testCount 124 | ) 125 | ); 126 | } 127 | 128 | 129 | Promise.all([ 130 | test1(), 131 | test2(), 132 | test3(), 133 | test4() 134 | ]).then( 135 | () => equals(testCount, 7) 136 | ); 137 | -------------------------------------------------------------------------------- /src/TaskQueue.ts: -------------------------------------------------------------------------------- 1 | // This file is part of cwait, copyright (c) 2015- BusFaster Ltd. 2 | // Released under the MIT license, see LICENSE. 3 | 4 | import { BinaryHeap } from 'cdata'; 5 | 6 | import { Task, PromisyClass, tryFinally } from './Task'; 7 | 8 | export class TaskQueue { 9 | 10 | constructor( 11 | private Promise: PromiseType, 12 | /** Number of promises allowed to resolve concurrently. */ 13 | public concurrency: number 14 | ) { 15 | this.Promise = Promise; 16 | this.concurrency = concurrency; 17 | this.nextBound = () => this.next(1); 18 | this.nextTickBound = () => { 19 | this.timerStamp = 0; 20 | this.next(0); 21 | }; 22 | } 23 | 24 | /** Add a new task to the queue. 25 | * It will start when the number of other concurrent tasks is low enough. 26 | * @param func Function to call. 27 | * @param delay Initial delay in milliseconds before making the call. */ 28 | 29 | add(func: () => any, delay = 0) { 30 | if(this.busyCount < this.concurrency && !delay) { 31 | // Start the task immediately. 32 | 33 | ++this.busyCount; 34 | 35 | return(tryFinally(func, this.nextBound, this.Promise)); 36 | } else { 37 | // Schedule the task and return a promise resolving 38 | // to the result of task.start(). 39 | 40 | const stamp = new Date().getTime() + delay; 41 | const task = new Task(func, this.Promise, stamp); 42 | 43 | this.backlog.insert(task); 44 | 45 | return(task.delay()); 46 | } 47 | } 48 | 49 | /** Consider current function idle until promise resolves. 50 | * Useful for making recursive calls. */ 51 | 52 | unblock(promise: PromiseLike) { 53 | this.next(1); 54 | 55 | const onFinish = () => ++this.busyCount; 56 | 57 | promise.then(onFinish, onFinish); 58 | 59 | return(promise); 60 | } 61 | 62 | /** Wrap a function returning a promise, so that before running 63 | * it waits until concurrent invocations are below this queue's limit. */ 64 | 65 | wrap(func: () => Result | PromiseLike, thisObject?: any): () => PromiseLike; 66 | wrap(func: (a: A) => Result | PromiseLike, thisObject?: any): (a: A) => PromiseLike; 67 | wrap(func: (a: A, b: B) => Result | PromiseLike, thisObject?: any): (a: A, b: B) => PromiseLike; 68 | wrap(func: (a: A, b: B, c: C) => Result | PromiseLike, thisObject?: any): (a: A, b: B, c: C) => PromiseLike; 69 | wrap(func: (a: A, b: B, c: C, d: D) => Result | PromiseLike, thisObject?: any): (a: A, b: B, c: C, d: D) => PromiseLike; 70 | wrap(func: (...args: any[]) => Result | PromiseLike, thisObject?: any): (...args: any[]) => PromiseLike { 71 | return((...args: any[]) => this.add(() => func.apply(thisObject, args))); 72 | } 73 | 74 | /** Start the next task from the backlog. */ 75 | 76 | private next(ended: number) { 77 | const stamp = new Date().getTime(); 78 | let task: Task | null = null; 79 | 80 | this.busyCount -= ended; 81 | 82 | // If another task is eligible to run, get the next scheduled task. 83 | if(this.busyCount < this.concurrency) task = this.backlog.peekTop(); 84 | 85 | if(!task) return; 86 | 87 | if(task.stamp <= stamp) { 88 | // A task remains, scheduled to start already. Resume it. 89 | ++this.busyCount; 90 | task = this.backlog.extractTop()!; 91 | task.resume(this.nextBound); 92 | } else if(!this.timerStamp || task.stamp + 1 < this.timerStamp) { 93 | // There is a task scheduled after a delay, 94 | // and no current timer firing before the delay. 95 | 96 | if(this.timerStamp) { 97 | // There is a timer firing too late. Remove it. 98 | clearTimeout(this.timer as any); 99 | } 100 | 101 | // Start a timer to clear timerStamp and call this function. 102 | this.timer = setTimeout(this.nextTickBound, ~~(task.stamp - stamp + 1)); 103 | this.timerStamp = task.stamp; 104 | } 105 | } 106 | 107 | private nextBound: () => void; 108 | private nextTickBound: () => void; 109 | 110 | private busyCount = 0; 111 | private backlog = new BinaryHeap>( 112 | (a: Task, b: Task) => a.stamp - b.stamp 113 | ); 114 | 115 | timer: number | NodeJS.Timer; 116 | timerStamp: number; 117 | 118 | } 119 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | cwait 2 | ===== 3 | 4 | [![build status](https://travis-ci.org/charto/cwait.svg?branch=master)](http://travis-ci.org/charto/cwait) 5 | [![npm monthly downloads](https://img.shields.io/npm/dm/cwait.svg)](https://www.npmjs.com/package/cwait) 6 | [![npm version](https://img.shields.io/npm/v/cwait.svg)](https://www.npmjs.com/package/cwait) 7 | 8 | `cwait` provides a queue handler ([`TaskQueue`](#api-TaskQueue)) and a wrapper ([`Task`](#api-Task)) for promises, 9 | to limit how many are being resolved simultaneously. It can wrap any ES6-compatible promises. 10 | This allows for example limiting simultaneous downloads with minor changes to existing code. 11 | Just wrap your existing "download finished" promise and use it as before. 12 | 13 | This is a tiny library with a single dependency, usable both in browsers and Node.js. 14 | 15 | Usage 16 | ----- 17 | 18 | Create a new `TaskQueue` passing it whatever `Promise` constructor you're using (ES6, Bluebird, some other shim...) 19 | and the maximum number of promise-returning functions to run concurrently. 20 | Then just call `queue.wrap()` instead of `` to limit simultaneous execution. 21 | 22 | Simple Node.js example: 23 | 24 | ```TypeScript 25 | import * as Promise from 'bluebird'; 26 | import {TaskQueue} from 'cwait'; 27 | 28 | /** Queue allowing 3 concurrent function calls. */ 29 | var queue = new TaskQueue(Promise, 3); 30 | 31 | Promise.map(list, download); // Download all listed files simultaneously. 32 | 33 | Promise.map(list, queue.wrap(download)); // Download 3 files at a time. 34 | ``` 35 | 36 | See [`test/test.ts`](test/test.ts) for some runnable code or run it like this: 37 | 38 | ```sh 39 | git clone https://github.com/charto/cwait.git 40 | cd cwait 41 | npm install 42 | npm test 43 | ``` 44 | 45 | Recursion 46 | --------- 47 | 48 | Recursive loops that run in parallel require special care. 49 | Nested concurrency-limited calls (that are not tail-recursive) must be wrapped in `queue.unblock()`. 50 | 51 | Here's a simple example that fails: 52 | 53 | ```JavaScript 54 | var queue = new (require('cwait').TaskQueue)(Promise, 3); 55 | 56 | var rec = queue.wrap(function(n) { 57 | console.log(n); 58 | return(n && Promise.resolve(rec(n - 1))); 59 | }); 60 | 61 | rec(10); 62 | ``` 63 | 64 | It only prints numbers 10, 9 and 8. 65 | More calls don't get scheduled because there are already 3 promises pending. 66 | For example Node.js exits immediately afterwards because the program is not blocked waiting for any system calls. 67 | 68 | Passing a promise to `queue.unblock(promise)` tells `queue` that 69 | the current function will wait for `promise` to resolve before continuing. 70 | One additional concurrent function is then allowed until the promise resolves. 71 | 72 | Be careful not to call `queue.unblock()` more than once (concurrently) from inside a wrapped function! 73 | Otherwise the queue may permit more simultaneous tasks than the intended limit. 74 | 75 | Here is a corrected example: 76 | 77 | ```JavaScript 78 | var queue = new (require('cwait').TaskQueue)(Promise, 3); 79 | 80 | var rec = queue.wrap(function(n) { 81 | console.log(n); 82 | return(n && queue.unblock(Promise.resolve(rec(n - 1)))); 83 | }); 84 | 85 | rec(10); 86 | ``` 87 | 88 | Advanced example with recursion 89 | ------------------------------- 90 | 91 | The following code recursively calculates the 10th Fibonacci number (55) 92 | running 3 recursive steps in parallel, each with an artificial 10-millisecond delay. 93 | 94 | At the end, it prints the result (55) and the number of concurrent calls (3). 95 | 96 | ```JavaScript 97 | var queue = new (require('cwait').TaskQueue)(Promise, 3); 98 | 99 | var maxRunning = 0; 100 | var running = 0; 101 | var delay = 10; 102 | 103 | var fib = queue.wrap(function(n) { 104 | // "Calculation" begins. Track maximum concurrent executions. 105 | if(++running > maxRunning) maxRunning = running; 106 | 107 | return(new Promise(function(resolve, reject) { 108 | setTimeout(function() { 109 | // "Calculation" ends. 110 | --running; 111 | 112 | // Each Fibonacci number is the sum of the previous two, except 113 | // the first ones are 0, 1 (starting from the 0th number). 114 | // Calculate them in parallel and unblock the queue until ready. 115 | 116 | resolve(n < 2 ? n : 117 | queue.unblock(Promise.all([ 118 | fib(n - 1), 119 | fib(n - 2) 120 | ])).then(function(r) { 121 | // Sum results from parallel recursion. 122 | return(r[0] + r[1]); 123 | }) 124 | ); 125 | }, delay); 126 | })); 127 | }); 128 | 129 | fib(10).then(function(x) { 130 | console.log('Result: ' + x); 131 | console.log('Concurrency: ' + maxRunning); 132 | }); 133 | ``` 134 | 135 | API 136 | === 137 | Docs generated using [`docts`](https://github.com/charto/docts) 138 | 139 | 140 | > 141 | > 142 | > ### Class [`Task`](#api-Task) 143 | > Task wraps a promise, delaying it until some resource gets less busy. 144 | > Source code: [`<>`](http://github.com/charto/cwait/blob/bcc3b2b/src/Task.ts#L49-L80) 145 | > 146 | > Methods: 147 | > > **new( )** [Task](#api-Task)<PromiseType> [`<>`](http://github.com/charto/cwait/blob/bcc3b2b/src/Task.ts#L50-L53) 148 | > >  ▪ func () => PromiseType 149 | > >  ▪ Promise [PromisyClass](#api-PromisyClass)<PromiseType> 150 | > > **.delay( )** PromiseType [`<>`](http://github.com/charto/cwait/blob/bcc3b2b/src/Task.ts#L57-L66) 151 | > >  Wrap task result in a new promise so it can be resolved later. 152 | > > **.resume( )** PromiseType [`<>`](http://github.com/charto/cwait/blob/bcc3b2b/src/Task.ts#L70-L72) 153 | > >  Start the task and call onFinish when done. 154 | > >  ▪ onFinish () => void 155 | > 156 | > 157 | > ### Class [`TaskQueue`](#api-TaskQueue) 158 | > Source code: [`<>`](http://github.com/charto/cwait/blob/c57c0fd/src/TaskQueue.ts#L6-L75) 159 | > 160 | > Methods: 161 | > > **new( )** [TaskQueue](#api-TaskQueue)<PromiseType> [`<>`](http://github.com/charto/cwait/blob/c57c0fd/src/TaskQueue.ts#L7-L11) 162 | > >  ▪ Promise [PromisyClass](#api-PromisyClass)<PromiseType> 163 | > >  ▪ concurrency number 164 | > > **.add( )** PromiseType [`<>`](http://github.com/charto/cwait/blob/c57c0fd/src/TaskQueue.ts#L16-L33) 165 | > >  Add a new task to the queue. 166 | > >  It will start when the number of other concurrent tasks is low enough. 167 | > >  ▪ func () => PromiseType 168 | > > **.unblock( )** PromiseType [`<>`](http://github.com/charto/cwait/blob/c57c0fd/src/TaskQueue.ts#L38-L46) 169 | > >  Consider current function idle until promise resolves. 170 | > >  Useful for making recursive calls. 171 | > >  ▪ promise PromiseType 172 | > > **.wrap( )** (...args: any[]) => PromiseType [`<>`](http://github.com/charto/cwait/blob/c57c0fd/src/TaskQueue.ts#L51-L53) 173 | > >  Wrap a function returning a promise, so that before running 174 | > >  it waits until concurrent invocations are below this queue's limit. 175 | > >  ▪ func (...args: any[]) => PromiseType 176 | > >  ▫ thisObject? any 177 | > 178 | > Properties: 179 | > > **.concurrency** number 180 | > >  Number of promises allowed to resolve concurrently. 181 | 182 | License 183 | ======= 184 | 185 | [The MIT License](https://raw.githubusercontent.com/charto/cwait/master/LICENSE) 186 | 187 | Copyright (c) 2015-2017 BusFaster Ltd 188 | --------------------------------------------------------------------------------