task-handler
Version:
Handle Javascript Timers like a boss! https://odo-network.github.io/task-handler/
632 lines (515 loc) • 18.5 kB
JavaScript
;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = createTaskHandler;
Object.defineProperty(exports, "TASK_CANCELLED", {
enumerable: true,
get: function get() {
return _constants.TASK_CANCELLED;
}
});
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
var _typeof2 = _interopRequireDefault(require("@babel/runtime/helpers/typeof"));
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
var _awaitAsyncGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/awaitAsyncGenerator"));
var _wrapAsyncGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/wrapAsyncGenerator"));
var _constants = require("./constants");
var _defer = _interopRequireDefault(require("./defer"));
function sequentialLoop(_x, _x2, _x3, _x4) {
return _sequentialLoop.apply(this, arguments);
}
function _sequentialLoop() {
_sequentialLoop = (0, _asyncToGenerator2.default)(_regenerator.default.mark(function _callee4(ref, next, execute, deferPromise) {
return _regenerator.default.wrap(function _callee4$(_context4) {
while (1) {
switch (_context4.prev = _context4.next) {
case 0:
if (!deferPromise) {
_context4.next = 5;
break;
}
_context4.next = 3;
return deferPromise;
case 3:
_context4.next = 5;
return execute();
case 5:
if (ref.status.complete) {
_context4.next = 14;
break;
}
_context4.next = 8;
return next();
case 8:
if (!ref.status.complete) {
_context4.next = 10;
break;
}
return _context4.abrupt("return");
case 10:
_context4.next = 12;
return execute();
case 12:
_context4.next = 5;
break;
case 14:
case "end":
return _context4.stop();
}
}
}, _callee4);
}));
return _sequentialLoop.apply(this, arguments);
}
function createTaskRef(type, id, handler, clearRef, jobDescriptor) {
var _ref;
handler.cancel(id);
var promise;
var promiseActions;
var lastResult;
var job;
var getNextPromise = function getNextPromise() {
if (promise) return promise;
promise = new Promise(function (resolve, reject) {
promiseActions = [resolve, reject];
});
return promise;
};
var getTaskError = function getTaskError(err) {
ref.status.error = true;
var error = (0, _typeof2.default)(err) === 'object' ? err : new Error(err);
error.taskRef = ref;
lastResult = error;
if (ref.type !== 'every') {
ref.cancel();
}
return error;
};
var ref = (_ref = {
get result() {
if (ref.status.complete) {
return lastResult;
}
return undefined;
},
get promise() {
return function () {
var _promisedResult = (0, _asyncToGenerator2.default)(_regenerator.default.mark(function _callee() {
var instancePromise;
return _regenerator.default.wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_context.prev = 0;
if (!ref.status.complete) {
_context.next = 5;
break;
}
if (!ref.status.error) {
_context.next = 4;
break;
}
throw lastResult;
case 4:
return _context.abrupt("return", ref);
case 5:
instancePromise = getNextPromise();
_context.next = 8;
return instancePromise;
case 8:
return _context.abrupt("return", ref);
case 9:
_context.prev = 9;
if (instancePromise === promise) {
promiseActions = undefined;
promise = undefined;
}
return _context.finish(9);
case 12:
case "end":
return _context.stop();
}
}
}, _callee, null, [[0,, 9, 12]]);
}));
function promisedResult() {
return _promisedResult.apply(this, arguments);
}
return promisedResult;
}();
},
get promises() {
if (type !== 'every') {
throw new Error("[ERROR] | task-handler | \"ref.promises()\" may only be used with iterative tasks such as every and everyNow, but tried with \"".concat(type, "\" task with ID: \"").concat(id, "\""));
}
return function () {
var _promiseIterator = (0, _wrapAsyncGenerator2.default)(_regenerator.default.mark(function _callee2() {
var instancePromise;
return _regenerator.default.wrap(function _callee2$(_context2) {
while (1) {
switch (_context2.prev = _context2.next) {
case 0:
_context2.prev = 0;
case 1:
if (ref.status.complete) {
_context2.next = 10;
break;
}
instancePromise = getNextPromise();
_context2.next = 5;
return (0, _awaitAsyncGenerator2.default)(instancePromise);
case 5:
_context2.next = 7;
return ref;
case 7:
if (instancePromise === promise) {
promiseActions = undefined;
promise = undefined;
}
_context2.next = 1;
break;
case 10:
return _context2.abrupt("return", ref);
case 11:
_context2.prev = 11;
if (instancePromise === promise) {
promiseActions = undefined;
promise = undefined;
}
return _context2.finish(11);
case 14:
case "end":
return _context2.stop();
}
}
}, _callee2, null, [[0,, 11, 14]]);
}));
function promiseIterator() {
return _promiseIterator.apply(this, arguments);
}
return promiseIterator;
}();
},
status: {
resolving: false,
complete: false,
error: false,
cancelled: false
}
}, (0, _defineProperty2.default)(_ref, _constants.TASK_CANCELLED, function (promises) {
if (ref.status.complete || ref.status.resolving) {
return;
}
ref.status.complete = true;
if (ref.status.error === false) {
lastResult = _constants.TASK_CANCELLED;
ref.status.cancelled = true;
if (promiseActions) {
promiseActions[0](ref);
}
if (job) {
if (typeof job.cancelled === 'function') {
promises.push(job.cancelled.call(ref, ref));
} else if (!job.complete && (typeof process === "undefined" ? "undefined" : (0, _typeof2.default)(process)) === 'object' && process.env.NODE_ENV !== 'production') {
console.warn("[WARN] | task-handler | Async Job \"".concat(id, "\" was cancelled but provided no \"cancelled\" or \"complete\" handler."));
}
if (job.complete) {
promises.push(job.complete.call(ref, ref));
}
}
}
}), (0, _defineProperty2.default)(_ref, _constants.EXECUTE_RESULT, function (err, result) {
if (!ref.status.complete) {
lastResult = result;
if (ref.type !== 'every') {
ref.status.complete = true;
}
if (err && !promiseActions) {
console.error("[ERROR] | task-handler | An unhandled error occurred while running a task with id \"".concat(ref.id, "\" with type \"").concat(ref.type, "\". If the errors can not be caught in the handler function that is called, these errors can be handled by calling 'ref.promise().catch()' or through async iteration handling if the task is using intervals.\n"), err);
return;
}
var error = err ? getTaskError(err) : undefined;
if (job) {
if (error && job.error) {
job.error.call(ref, error);
}
if (job.complete) {
job.complete.call(ref, ref);
}
}
if (promiseActions) {
if (error) return promiseActions[1](error);
return promiseActions[0](ref);
}
}
}), (0, _defineProperty2.default)(_ref, "id", id), (0, _defineProperty2.default)(_ref, "type", type), (0, _defineProperty2.default)(_ref, "task", handler), (0, _defineProperty2.default)(_ref, "resolve", function resolve(result) {
ref.status.resolving = true;
clearRef(id);
return ref[_constants.EXECUTE_RESULT](undefined, result);
}), (0, _defineProperty2.default)(_ref, "reject", function reject(err) {
ref.status.resolving = true;
clearRef(id);
return ref[_constants.EXECUTE_RESULT](err);
}), (0, _defineProperty2.default)(_ref, "cancel", function cancel() {
if (!ref.status.complete && !ref.status.resolving) {
if (ref.status.error === false) {
lastResult = _constants.TASK_CANCELLED;
ref.status.cancelled = true;
}
handler.cancel(id);
}
}), _ref);
if (jobDescriptor) {
var _jobDescriptor = (0, _slicedToArray2.default)(jobDescriptor, 3),
getJob = _jobDescriptor[0],
_args3 = _jobDescriptor[1],
refs = _jobDescriptor[2];
job = getJob.call.apply(getJob, [ref, ref].concat((0, _toConsumableArray2.default)(_args3)));
ref.promise().catch(_constants.NOOP);
refs.set(id, [ref, _constants.NOOP]);
job.start.call(ref, ref);
}
return Object.freeze(ref);
}
function createTaskHandler() {
var refs = new Map();
var queue;
function getQueue() {
if (queue) {
return queue;
}
queue = (0, _defer.default)(refs);
return queue;
}
function clearRef(id, withRef) {
var descriptor = refs.get(id);
if (!descriptor) return;
try {
var _descriptor = (0, _slicedToArray2.default)(descriptor, 2),
ref = _descriptor[0],
canceller = _descriptor[1];
if (!withRef || withRef === ref) {
try {
canceller();
} catch (err) {
console.error('[task-handler] | ERROR | Failed to call canceller for ref with id: ', id);
}
}
return ref;
} finally {
refs.delete(id);
}
}
function cancelID(id, promises) {
var ref = clearRef(id);
if (ref) {
ref[_constants.TASK_CANCELLED](promises);
}
}
function execute(ref, fn, args) {
try {
if (ref.type !== 'every') {
ref.status.resolving = true;
clearRef(ref.id);
}
var result = typeof fn === 'function' ? fn.apply(ref, args) : undefined;
ref[_constants.EXECUTE_RESULT](undefined, result);
} catch (e) {
ref[_constants.EXECUTE_RESULT](e);
}
}
function asyncExecute(_x5, _x6, _x7) {
return _asyncExecute.apply(this, arguments);
}
function _asyncExecute() {
_asyncExecute = (0, _asyncToGenerator2.default)(_regenerator.default.mark(function _callee3(ref, fn, args) {
var result;
return _regenerator.default.wrap(function _callee3$(_context3) {
while (1) {
switch (_context3.prev = _context3.next) {
case 0:
_context3.prev = 0;
if (ref.type !== 'every') {
ref.status.resolving = true;
clearRef(ref.id);
}
if (!(typeof fn === 'function')) {
_context3.next = 8;
break;
}
_context3.next = 5;
return fn.apply(ref, args);
case 5:
_context3.t0 = _context3.sent;
_context3.next = 9;
break;
case 8:
_context3.t0 = undefined;
case 9:
result = _context3.t0;
_context3.next = 12;
return ref[_constants.EXECUTE_RESULT](undefined, result);
case 12:
_context3.next = 18;
break;
case 14:
_context3.prev = 14;
_context3.t1 = _context3["catch"](0);
_context3.next = 18;
return ref[_constants.EXECUTE_RESULT](_context3.t1);
case 18:
case "end":
return _context3.stop();
}
}
}, _callee3, null, [[0, 14]]);
}));
return _asyncExecute.apply(this, arguments);
}
var handler = Object.freeze({
get size() {
return refs.size;
},
has: function has() {
for (var _len = arguments.length, ids = new Array(_len), _key = 0; _key < _len; _key++) {
ids[_key] = arguments[_key];
}
return ids.every(function (id) {
return refs.has(id);
});
},
after: function after(id, delay, fn) {
var ref = createTaskRef('after', id, handler, clearRef);
for (var _len2 = arguments.length, args = new Array(_len2 > 3 ? _len2 - 3 : 0), _key2 = 3; _key2 < _len2; _key2++) {
args[_key2 - 3] = arguments[_key2];
}
var timeoutID = setTimeout(execute, delay, ref, fn, args);
refs.set(id, [ref, function () {
return clearTimeout(timeoutID);
}]);
return ref;
},
defer: function defer(id, fn) {
for (var _len3 = arguments.length, args = new Array(_len3 > 2 ? _len3 - 2 : 0), _key3 = 2; _key3 < _len3; _key3++) {
args[_key3 - 2] = arguments[_key3];
}
var ref = createTaskRef('defer', id, handler, clearRef);
var cancelDefer = getQueue().add(ref, function () {
return execute(ref, fn, args);
});
refs.set(id, [ref, function () {
return cancelDefer();
}]);
return ref;
},
every: function every(id, interval, fn) {
var ref = createTaskRef('every', id, handler, clearRef);
for (var _len4 = arguments.length, args = new Array(_len4 > 3 ? _len4 - 3 : 0), _key4 = 3; _key4 < _len4; _key4++) {
args[_key4 - 3] = arguments[_key4];
}
var timerID = setInterval(execute, interval, ref, fn, args);
refs.set(id, [ref, function () {
return clearInterval(timerID);
}]);
return ref;
},
everyNow: function everyNow(id, interval, fn) {
for (var _len5 = arguments.length, args = new Array(_len5 > 3 ? _len5 - 3 : 0), _key5 = 3; _key5 < _len5; _key5++) {
args[_key5 - 3] = arguments[_key5];
}
var ref = createTaskRef('every', id, handler, clearRef);
var timerID = setInterval(execute, interval, ref, fn, args);
var cancelDefer = getQueue().add(id, function () {
return execute(ref, fn, args);
});
refs.set(id, [ref, function () {
clearInterval(timerID);
cancelDefer();
}]);
return ref;
},
everyNowSequential: function everyNowSequential(id, interval, fn) {
for (var _len6 = arguments.length, args = new Array(_len6 > 3 ? _len6 - 3 : 0), _key6 = 3; _key6 < _len6; _key6++) {
args[_key6 - 3] = arguments[_key6];
}
var timerID;
var resolveNext;
var ref = createTaskRef('every', id, handler, clearRef);
var deferPromise = new Promise(function (resolve) {
resolveNext = resolve;
});
var cancelDefer = getQueue().add(id, function () {
resolveNext();
});
refs.set(id, [ref, function () {
cancelDefer();
clearTimeout(timerID);
resolveNext();
}]);
var next = function next() {
return new Promise(function (resolve) {
resolveNext = resolve;
timerID = setTimeout(resolve, interval);
});
};
var executeNext = function executeNext() {
return asyncExecute(ref, fn, args);
};
sequentialLoop(ref, next, executeNext, deferPromise);
return ref;
},
everySequential: function everySequential(id, interval, fn) {
for (var _len7 = arguments.length, args = new Array(_len7 > 3 ? _len7 - 3 : 0), _key7 = 3; _key7 < _len7; _key7++) {
args[_key7 - 3] = arguments[_key7];
}
var timerID;
var resolveNext;
var ref = createTaskRef('every', id, handler, clearRef);
refs.set(id, [ref, function () {
clearTimeout(timerID);
resolveNext();
}]);
var next = function next() {
return new Promise(function (resolve) {
resolveNext = resolve;
timerID = setTimeout(resolve, interval);
});
};
var executeNext = function executeNext() {
return asyncExecute(ref, fn, args);
};
sequentialLoop(ref, next, executeNext);
return ref;
},
job: function job(id, getJob) {
for (var _len8 = arguments.length, args = new Array(_len8 > 2 ? _len8 - 2 : 0), _key8 = 2; _key8 < _len8; _key8++) {
args[_key8 - 2] = arguments[_key8];
}
var ref = createTaskRef('job', id, handler, clearRef, [getJob, args || _constants.STATIC_EMPTY_ARRAY, refs]);
return ref;
},
cancel: function cancel() {
var promises = [];
for (var _len9 = arguments.length, ids = new Array(_len9), _key9 = 0; _key9 < _len9; _key9++) {
ids[_key9] = arguments[_key9];
}
ids.forEach(function (id) {
return cancelID(id, promises);
});
return {
promise: function promise() {
return Promise.all(promises);
}
};
},
clear: function clear() {
return handler.cancel.apply(handler, (0, _toConsumableArray2.default)(Array.from(refs.keys())));
}
});
return handler;
}