313 lines
6.2 KiB
JavaScript
313 lines
6.2 KiB
JavaScript
'use strict';
|
|
|
|
/* eslint-disable no-var */
|
|
|
|
var reusify = require('reusify');
|
|
|
|
function fastqueue(context, worker, _concurrency) {
|
|
if (typeof context === 'function') {
|
|
_concurrency = worker;
|
|
worker = context;
|
|
context = null;
|
|
}
|
|
|
|
if (!(_concurrency >= 1)) {
|
|
throw new Error('fastqueue concurrency must be equal to or greater than 1');
|
|
}
|
|
|
|
var cache = reusify(Task);
|
|
var queueHead = null;
|
|
var queueTail = null;
|
|
var _running = 0;
|
|
var errorHandler = null;
|
|
|
|
var self = {
|
|
push: push,
|
|
drain: noop,
|
|
saturated: noop,
|
|
pause: pause,
|
|
paused: false,
|
|
|
|
get concurrency() {
|
|
return _concurrency;
|
|
},
|
|
set concurrency(value) {
|
|
if (!(value >= 1)) {
|
|
throw new Error(
|
|
'fastqueue concurrency must be equal to or greater than 1'
|
|
);
|
|
}
|
|
_concurrency = value;
|
|
|
|
if (self.paused) return;
|
|
for (; queueHead && _running < _concurrency; ) {
|
|
_running++;
|
|
release();
|
|
}
|
|
},
|
|
|
|
running: running,
|
|
resume: resume,
|
|
idle: idle,
|
|
length: length,
|
|
getQueue: getQueue,
|
|
unshift: unshift,
|
|
empty: noop,
|
|
kill: kill,
|
|
killAndDrain: killAndDrain,
|
|
error: error,
|
|
};
|
|
|
|
return self;
|
|
|
|
function running() {
|
|
return _running;
|
|
}
|
|
|
|
function pause() {
|
|
self.paused = true;
|
|
}
|
|
|
|
function length() {
|
|
var current = queueHead;
|
|
var counter = 0;
|
|
|
|
while (current) {
|
|
current = current.next;
|
|
counter++;
|
|
}
|
|
|
|
return counter;
|
|
}
|
|
|
|
function getQueue() {
|
|
var current = queueHead;
|
|
var tasks = [];
|
|
|
|
while (current) {
|
|
tasks.push(current.value);
|
|
current = current.next;
|
|
}
|
|
|
|
return tasks;
|
|
}
|
|
|
|
function resume() {
|
|
if (!self.paused) return;
|
|
self.paused = false;
|
|
if (queueHead === null) {
|
|
_running++;
|
|
release();
|
|
return;
|
|
}
|
|
for (; queueHead && _running < _concurrency; ) {
|
|
_running++;
|
|
release();
|
|
}
|
|
}
|
|
|
|
function idle() {
|
|
return _running === 0 && self.length() === 0;
|
|
}
|
|
|
|
function push(value, done) {
|
|
var current = cache.get();
|
|
|
|
current.context = context;
|
|
current.release = release;
|
|
current.value = value;
|
|
current.callback = done || noop;
|
|
current.errorHandler = errorHandler;
|
|
|
|
if (_running >= _concurrency || self.paused) {
|
|
if (queueTail) {
|
|
queueTail.next = current;
|
|
queueTail = current;
|
|
} else {
|
|
queueHead = current;
|
|
queueTail = current;
|
|
self.saturated();
|
|
}
|
|
} else {
|
|
_running++;
|
|
worker.call(context, current.value, current.worked);
|
|
}
|
|
}
|
|
|
|
function unshift(value, done) {
|
|
var current = cache.get();
|
|
|
|
current.context = context;
|
|
current.release = release;
|
|
current.value = value;
|
|
current.callback = done || noop;
|
|
current.errorHandler = errorHandler;
|
|
|
|
if (_running >= _concurrency || self.paused) {
|
|
if (queueHead) {
|
|
current.next = queueHead;
|
|
queueHead = current;
|
|
} else {
|
|
queueHead = current;
|
|
queueTail = current;
|
|
self.saturated();
|
|
}
|
|
} else {
|
|
_running++;
|
|
worker.call(context, current.value, current.worked);
|
|
}
|
|
}
|
|
|
|
function release(holder) {
|
|
if (holder) {
|
|
cache.release(holder);
|
|
}
|
|
var next = queueHead;
|
|
if (next && _running <= _concurrency) {
|
|
if (!self.paused) {
|
|
if (queueTail === queueHead) {
|
|
queueTail = null;
|
|
}
|
|
queueHead = next.next;
|
|
next.next = null;
|
|
worker.call(context, next.value, next.worked);
|
|
if (queueTail === null) {
|
|
self.empty();
|
|
}
|
|
} else {
|
|
_running--;
|
|
}
|
|
} else if (--_running === 0) {
|
|
self.drain();
|
|
}
|
|
}
|
|
|
|
function kill() {
|
|
queueHead = null;
|
|
queueTail = null;
|
|
self.drain = noop;
|
|
}
|
|
|
|
function killAndDrain() {
|
|
queueHead = null;
|
|
queueTail = null;
|
|
self.drain();
|
|
self.drain = noop;
|
|
}
|
|
|
|
function error(handler) {
|
|
errorHandler = handler;
|
|
}
|
|
}
|
|
|
|
function noop() {}
|
|
|
|
function Task() {
|
|
this.value = null;
|
|
this.callback = noop;
|
|
this.next = null;
|
|
this.release = noop;
|
|
this.context = null;
|
|
this.errorHandler = null;
|
|
|
|
var self = this;
|
|
|
|
this.worked = function worked(err, result) {
|
|
var callback = self.callback;
|
|
var errorHandler = self.errorHandler;
|
|
var val = self.value;
|
|
self.value = null;
|
|
self.callback = noop;
|
|
if (self.errorHandler) {
|
|
errorHandler(err, val);
|
|
}
|
|
callback.call(self.context, err, result);
|
|
self.release(self);
|
|
};
|
|
}
|
|
|
|
function queueAsPromised(context, worker, _concurrency) {
|
|
if (typeof context === 'function') {
|
|
_concurrency = worker;
|
|
worker = context;
|
|
context = null;
|
|
}
|
|
|
|
function asyncWrapper(arg, cb) {
|
|
worker.call(this, arg).then(function (res) {
|
|
cb(null, res);
|
|
}, cb);
|
|
}
|
|
|
|
var queue = fastqueue(context, asyncWrapper, _concurrency);
|
|
|
|
var pushCb = queue.push;
|
|
var unshiftCb = queue.unshift;
|
|
|
|
queue.push = push;
|
|
queue.unshift = unshift;
|
|
queue.drained = drained;
|
|
|
|
return queue;
|
|
|
|
function push(value) {
|
|
var p = new Promise(function (resolve, reject) {
|
|
pushCb(value, function (err, result) {
|
|
if (err) {
|
|
reject(err);
|
|
return;
|
|
}
|
|
resolve(result);
|
|
});
|
|
});
|
|
|
|
// Let's fork the promise chain to
|
|
// make the error bubble up to the user but
|
|
// not lead to a unhandledRejection
|
|
p.catch(noop);
|
|
|
|
return p;
|
|
}
|
|
|
|
function unshift(value) {
|
|
var p = new Promise(function (resolve, reject) {
|
|
unshiftCb(value, function (err, result) {
|
|
if (err) {
|
|
reject(err);
|
|
return;
|
|
}
|
|
resolve(result);
|
|
});
|
|
});
|
|
|
|
// Let's fork the promise chain to
|
|
// make the error bubble up to the user but
|
|
// not lead to a unhandledRejection
|
|
p.catch(noop);
|
|
|
|
return p;
|
|
}
|
|
|
|
function drained() {
|
|
var p = new Promise(function (resolve) {
|
|
process.nextTick(function () {
|
|
if (queue.idle()) {
|
|
resolve();
|
|
} else {
|
|
var previousDrain = queue.drain;
|
|
queue.drain = function () {
|
|
if (typeof previousDrain === 'function') previousDrain();
|
|
resolve();
|
|
queue.drain = previousDrain;
|
|
};
|
|
}
|
|
});
|
|
});
|
|
|
|
return p;
|
|
}
|
|
}
|
|
|
|
module.exports = fastqueue;
|
|
module.exports.promise = queueAsPromised;
|