UNPKG

cwait

Version:

Limit number of promises running in parallel

267 lines (259 loc) 10.2 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : (factory((global.cwait = {}))); }(this, (function (exports) { 'use strict'; // This file is part of cwait, copyright (c) 2015- BusFaster Ltd. // Released under the MIT license, see LICENSE. /** Call func and return a promise for its result. * Optionally call given resolve or reject handler when the promise settles. */ function tryFinally(func, onFinish, Promise, resolve, reject) { var promise; try { promise = Promise.resolve(func()); } catch (err) { // If func threw an error, return a rejected promise. promise = Promise.reject(err); } promise.then(onFinish, onFinish); if (resolve) promise.then(resolve, reject); return (promise); } /** Task wraps a promise, delaying it until some resource gets less busy. */ var Task = /** @class */ (function () { function Task(func, Promise, stamp) { this.func = func; this.Promise = Promise; this.stamp = stamp; } /** Wrap task result in a new promise so it can be resolved later. */ Task.prototype.delay = function () { var _this = this; if (!this.promise) { this.promise = new this.Promise(function (resolve, reject) { _this.resolve = resolve; _this.reject = reject; }); } return (this.promise); }; /** Start the task and call onFinish when done. */ Task.prototype.resume = function (onFinish) { return (tryFinally(this.func, onFinish, this.Promise, this.resolve, this.reject)); }; return Task; }()); // This file is part of cdata, copyright (c) 2017- BusFaster Ltd. // Released under the MIT license, see LICENSE. var BinaryHeap = /** @class */ (function () { /** @param compare Compare two items, as used with Array.sort. */ function BinaryHeap(compare, _a) { var _b = _a === void 0 ? {} : _a, getPos = _b.getPos, setPos = _b.setPos; this.compare = compare; this.heap = []; this.last = 0; this.getPos = getPos; this.setPos = setPos || (function (item, pos) { }); } /** Erase all contents of the heap. */ BinaryHeap.prototype.clear = function () { this.heap = []; this.last = 0; }; BinaryHeap.prototype.isEmpty = function () { return (!this.last); }; /** Insert a new item in the heap. * @return Index of the inserted item in the heap's internal array. */ BinaryHeap.prototype.insert = function (item) { return (this.bubble(item, this.last++)); }; /** Update the position of an item in the heap. */ BinaryHeap.prototype.update = function (item) { var getPos = this.getPos; if (!getPos) throw (new Error('Cannot get index of item in the heap')); var pos = getPos(item); var posParent = (pos - 1) >>> 1; var delta = this.compare(this.heap[posParent], item); if (delta > 0) this.bubble(item, pos); else if (delta < 0) this.sink(item, pos); return (item); }; /** Get the top item of the heap without removing it. */ BinaryHeap.prototype.peekTop = function () { return (!this.last ? null : this.heap[0]); }; /** Remove and return the top item of the heap. */ BinaryHeap.prototype.extractTop = function () { if (!this.last) return (null); var top = this.heap[0]; var bottom = this.heap[--this.last]; // Erase last item. this.heap[this.last] = void 0; this.sink(bottom, 0); return (top); }; /** Move an item upwards in the heap, to its correct position. * @param item Item to move. * @param pos Index of the item in the heap's internal array. */ BinaryHeap.prototype.bubble = function (item, pos) { var heap = this.heap; var compare = this.compare; var setPos = this.setPos; var posParent; var parent; while (pos > 0) { posParent = (pos - 1) >>> 1; parent = heap[posParent]; if (compare(parent, item) <= 0) break; heap[pos] = parent; setPos(parent, pos); pos = posParent; } heap[pos] = item; setPos(item, pos); }; /** Move an item downwards in the heap, to its correct position. * @param item Item to move. * @param pos Index of the item in the heap's internal array. */ BinaryHeap.prototype.sink = function (item, pos) { var heap = this.heap; var last = this.last; var compare = this.compare; var setPos = this.setPos; var posLeft; var posRight; var posNext; var left; var right; while (1) { posLeft = pos * 2 + 1; posRight = pos * 2 + 2; if (posRight < last) { left = heap[posLeft]; right = heap[posRight]; if (compare(left, right) < 0) { if (compare(left, item) < 0) posNext = posLeft; else break; } else { if (compare(right, item) < 0) posNext = posRight; else break; } } else { if (posLeft < last && compare(heap[posLeft], item) < 0) posNext = posLeft; else break; } heap[pos] = heap[posNext]; setPos(heap[pos], pos); pos = posNext; } heap[pos] = item; setPos(item, pos); }; return BinaryHeap; }()); // This file is part of cwait, copyright (c) 2015- BusFaster Ltd. var TaskQueue = /** @class */ (function () { function TaskQueue(Promise, /** Number of promises allowed to resolve concurrently. */ concurrency) { var _this = this; this.Promise = Promise; this.concurrency = concurrency; this.busyCount = 0; this.backlog = new BinaryHeap(function (a, b) { return a.stamp - b.stamp; }); this.Promise = Promise; this.concurrency = concurrency; this.nextBound = function () { return _this.next(1); }; this.nextTickBound = function () { _this.timerStamp = 0; _this.next(0); }; } /** Add a new task to the queue. * It will start when the number of other concurrent tasks is low enough. * @param func Function to call. * @param delay Initial delay in milliseconds before making the call. */ TaskQueue.prototype.add = function (func, delay) { if (delay === void 0) { delay = 0; } if (this.busyCount < this.concurrency && !delay) { // Start the task immediately. ++this.busyCount; return (tryFinally(func, this.nextBound, this.Promise)); } else { // Schedule the task and return a promise resolving // to the result of task.start(). var stamp = new Date().getTime() + delay; var task = new Task(func, this.Promise, stamp); this.backlog.insert(task); return (task.delay()); } }; /** Consider current function idle until promise resolves. * Useful for making recursive calls. */ TaskQueue.prototype.unblock = function (promise) { var _this = this; this.next(1); var onFinish = function () { return ++_this.busyCount; }; promise.then(onFinish, onFinish); return (promise); }; TaskQueue.prototype.wrap = function (func, thisObject) { var _this = this; return (function () { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } return _this.add(function () { return func.apply(thisObject, args); }); }); }; /** Start the next task from the backlog. */ TaskQueue.prototype.next = function (ended) { var stamp = new Date().getTime(); var task = null; this.busyCount -= ended; // If another task is eligible to run, get the next scheduled task. if (this.busyCount < this.concurrency) task = this.backlog.peekTop(); if (!task) return; if (task.stamp <= stamp) { // A task remains, scheduled to start already. Resume it. ++this.busyCount; task = this.backlog.extractTop(); task.resume(this.nextBound); } else if (!this.timerStamp || task.stamp + 1 < this.timerStamp) { // There is a task scheduled after a delay, // and no current timer firing before the delay. if (this.timerStamp) { // There is a timer firing too late. Remove it. clearTimeout(this.timer); } // Start a timer to clear timerStamp and call this function. this.timer = setTimeout(this.nextTickBound, ~~(task.stamp - stamp + 1)); this.timerStamp = task.stamp; } }; return TaskQueue; }()); // This file is part of cwait, copyright (c) 2015- BusFaster Ltd. exports.Task = Task; exports.TaskQueue = TaskQueue; Object.defineProperty(exports, '__esModule', { value: true }); })));