cwait
Version:
Limit number of promises running in parallel
267 lines (259 loc) • 10.2 kB
JavaScript
(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 });
})));