nope-js-node
Version:
NoPE Runtime for Nodejs. For Browser-Support please use nope-browser
196 lines (195 loc) • 8.3 kB
JavaScript
;
/**
* @author Martin Karkowski
* @email m.karkowski@zema.de
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.limitedCalls = exports.getLimitedOptions = void 0;
const events_1 = require("events");
const index_browser_1 = require("../index.browser");
const idMethods_1 = require("./idMethods");
const setMethods_1 = require("./setMethods");
/**
* Helper to get the default options in a shared context
* @param options The options to enhance the defaults.
* @returns The options.
*/
function getLimitedOptions(options) {
const defaultSettings = {
queue: [],
mapping: {},
emitter: new events_1.EventEmitter(),
maxParallel: 0,
loggerLevel: false,
activeTasks: new Set(),
awaitingTasks: new Set(),
};
return Object.assign(defaultSettings, options);
}
exports.getLimitedOptions = getLimitedOptions;
/**
* Function to limit the calls based on the settings.
* @param func The function to use. This should be an async function.
* @param options The Options.
* @returns
*/
function limitedCalls(func, options) {
let logger = false;
// Define the Default-Settings
const defaultSettins = {
functionId: Date.now().toString(),
queue: [],
mapping: {},
emitter: new events_1.EventEmitter(),
getLock: () => {
const tasks = (0, setMethods_1.difference)(settingsToUse.activeTasks, settingsToUse.awaitingTasks);
if (logger) {
logger[settingsToUse.loggerLevel](`active Tasks: [${Array.from(tasks)}]; awaiting Tasks: [${Array.from(settingsToUse.awaitingTasks)}];`);
}
return (settingsToUse.maxParallel < 0 || tasks.size <= settingsToUse.maxParallel);
},
assignControlFunction: (args, opts) => {
return args;
},
maxParallel: 0,
loggerLevel: false,
activeTasks: new Set(),
awaitingTasks: new Set(),
minDelay: -1,
lastDone: Date.now(),
};
const settingsToUse = Object.assign(defaultSettins, options);
const functionId = settingsToUse.functionId;
settingsToUse.mapping[functionId] = func;
if (settingsToUse.loggerLevel) {
logger = (0, index_browser_1.getNopeLogger)("limited-calls", settingsToUse.loggerLevel);
}
const wrapped = function (...args) {
// Generate the Call-ID
const taskId = (0, idMethods_1.generateId)();
const pauseTask = () => {
if (logger) {
logger[settingsToUse.loggerLevel](`pausing taskId="${taskId}".`);
}
settingsToUse.awaitingTasks.add(taskId);
settingsToUse.emitter.emit("execute");
};
const continueTask = () => {
if (logger) {
logger[settingsToUse.loggerLevel](`continuing taskId="${taskId}".`);
}
settingsToUse.awaitingTasks.delete(taskId);
settingsToUse.emitter.emit("execute");
};
// Add the functions.
args = settingsToUse.assignControlFunction(args, {
pauseTask,
continueTask,
});
// Push the Content to the emitter
settingsToUse.queue.push([functionId, taskId, args]);
// lets have an item, that contains the resolve
let resolve = null;
let reject = null;
// Define a callback, which is called.
const cb = (error, result) => {
settingsToUse.emitter.off(taskId, cb);
if (error) {
reject(error);
}
else {
resolve(result);
}
// Delete the Task
settingsToUse.activeTasks.delete(taskId);
settingsToUse.awaitingTasks.delete(taskId);
if (typeof settingsToUse.callbackBetween === "function") {
if (logger) {
logger[settingsToUse.loggerLevel](`using 'callbackBetween' for taskId="${taskId}". Calling now`);
}
settingsToUse
.callbackBetween()
.then((_) => {
if (logger) {
logger[settingsToUse.loggerLevel](`awaited 'callbackBetween' for taskId="${taskId}". Transmitting results now`);
}
settingsToUse.lastDone = Date.now();
// Emit, that there is a new task available
settingsToUse.emitter.emit("execute");
})
.catch((_) => {
// Log some stuff
if (logger) {
logger[settingsToUse.loggerLevel](`something went wrong with 'callbackBetween' for taskId="${taskId}". Transmitting results now!`);
}
settingsToUse.lastDone = Date.now();
// Emit, that there is a new task available
settingsToUse.emitter.emit("execute");
});
}
else {
if (logger) {
logger[settingsToUse.loggerLevel](`no 'callbackBetween' for taskId="${taskId}". Transmitting results now`);
}
settingsToUse.lastDone = Date.now();
// Emit, that there is a new task available
settingsToUse.emitter.emit("execute");
}
};
settingsToUse.emitter.on(taskId, cb);
const promise = new Promise((_resolve, _reject) => {
resolve = _resolve;
reject = _reject;
});
settingsToUse.emitter.emit("execute");
return promise;
};
if (settingsToUse.emitter.listeners("execute").length == 0) {
const tryExecuteTask = () => {
if (settingsToUse.queue.length > 0) {
// Get the Id and the Args.
const [functionId, taskId, args] = settingsToUse.queue[0];
if (settingsToUse.getLock(functionId, taskId)) {
const diff = Date.now() - settingsToUse.lastDone;
if (settingsToUse.minDelay > 0 && diff < settingsToUse.minDelay) {
// Recall our routine
setTimeout(tryExecuteTask, settingsToUse.minDelay - diff + 10, null);
return;
}
if (settingsToUse.maxParallel > 0) {
settingsToUse.lastDone = Date.now();
}
// Add the Task as active.
settingsToUse.activeTasks.add(taskId);
// Remove the items:
settingsToUse.queue.splice(0, 1);
// Try to perform the call.
try {
if (logger) {
logger[settingsToUse.loggerLevel](`calling function '${functionId}' for the task taskId="${taskId}"`);
}
settingsToUse.mapping[functionId](...args)
.then((result) => {
if (logger) {
logger[settingsToUse.loggerLevel](`called function '${functionId}' for the task taskId="${taskId}"`);
}
settingsToUse.emitter.emit(taskId, null, result);
})
.catch((error) => {
if (logger) {
logger[settingsToUse.loggerLevel](`called function '${functionId}' for the task taskId="${taskId}", but resulted in an error`);
}
settingsToUse.emitter.emit(taskId, error, null);
});
}
catch (error) {
settingsToUse.emitter.emit(taskId, error, null);
}
}
}
};
settingsToUse.emitter.on("execute", tryExecuteTask);
}
return wrapped;
}
exports.limitedCalls = limitedCalls;