fix(core): update

This commit is contained in:
Philipp Kunz 2023-08-02 00:51:43 +02:00
parent fa8be6b6d3
commit 5240a80cb3
3 changed files with 104 additions and 160 deletions

View File

@ -1,116 +1,96 @@
import { expect, tap } from '@push.rocks/tapbundle'; import { expect, tap } from '@push.rocks/tapbundle';
import * as taskbuffer from '../ts/index.js'; import * as taskbuffer from '../ts/index.js';
import * as smartpromise from '@push.rocks/smartpromise'; import * as smartpromise from '@push.rocks/smartpromise';
import * as smartdelay from '@push.rocks/smartdelay'; import * as smartdelay from '@push.rocks/smartdelay';
// setup some testData to work with
let testTask: taskbuffer.Task;
let testPreTask = new taskbuffer.Task({
taskFunction: async () => {
console.log('preTask executed');
},
preTask: testTask,
});
// some more tasks to test with
let task1Counter = 0; // how often task 1 is being executed
let task1 = new taskbuffer.Task({
name: 'Task 1',
taskFunction: () => {
let done = smartpromise.defer();
console.log('Task1 started');
setTimeout(() => {
task1Counter++;
console.log('Task1 executed');
done.resolve();
}, 5000);
return done.promise;
},
});
let task2 = new taskbuffer.Task({
name: 'Task 1',
taskFunction: async () => {
const done = smartpromise.defer();
console.log('Task2 started');
setTimeout(() => {
console.log('Task2 executed');
done.resolve();
}, 5000);
await done.promise;
},
});
let task3 = new taskbuffer.Task({
name: 'Task 3',
taskFunction: () => {
let done = smartpromise.defer();
console.log('Task3 started');
setTimeout(() => {
console.log('Task3 executed');
done.resolve();
}, 5000);
return done.promise;
},
});
tap.test('new Task() should return a new task', async () => { tap.test('new Task() should return a new task', async () => {
testTask = new taskbuffer.Task({ const testTask = new taskbuffer.Task({
taskFunction: async () => { taskFunction: async () => {
console.log('executed twice'); console.log('executed twice');
}, },
preTask: testPreTask,
}); });
});
tap.test('expect testTask to be an instance of Task', async () => {
expect(testTask).toBeInstanceOf(taskbuffer.Task); expect(testTask).toBeInstanceOf(taskbuffer.Task);
}); });
tap.test('expect testTask.idle is true', async () => { tap.test('should be able to get the task state', async () => {
if (!testTask.idle) { const testTask = new taskbuffer.Task({
throw new Error('testTask.idle is not true'); taskFunction: async () => {},
} });
expect(testTask.state).toEqual('ready');
}); });
tap.test('testTask.running should be of type boolean and initially false', async () => { tap.test('should have bufferMax set to the provided value', async () => {
expect(testTask.running).toBeTypeofBoolean(); const task2 = new taskbuffer.Task({
// tslint:disable-next-line:no-unused-expression taskFunction: async () => {},
expect(testTask.running).toBeFalse(); });
expect(task2.bufferMax).toBeUndefined(); // test for a task without bufferMax set
const bufferedTask = new taskbuffer.Task({
taskFunction: async () => {},
buffered: true,
bufferMax: 3,
});
expect(bufferedTask.bufferMax).toEqual(3);
}); });
tap.test('testTask.trigger() should return Promise', async () => { tap.test('should be able to trigger tasks multiple times', async () => {
expect(testTask.trigger()).toBeInstanceOf(Promise); let task1Counter = 0;
}); const task1 = new taskbuffer.Task({
name: 'Task 1',
tap.test('testTask.trigger() returned Promise should be fullfilled', async () => { taskFunction: () => {
await testTask.trigger(); let done = smartpromise.defer();
}); console.log('Task1 started');
setTimeout(() => {
tap.test('expect to run a task without pre and afterTask errorless', async () => { task1Counter++;
let localTestTask = new taskbuffer.Task({ console.log('Task1 executed');
taskFunction: async () => { done.resolve();
console.log('only once'); }, 5000);
return done.promise;
}, },
}); });
await localTestTask.trigger(); await task1.trigger();
await task1.trigger();
expect(task1Counter).toEqual(2);
}); });
tap.test('expect task to run in buffered mode', async () => { tap.test('should execute setup function before the task function', async () => {
let localTestTask = new taskbuffer.Task({ const task2 = new taskbuffer.Task({
name: 'Task 2',
taskSetup: async () => {
console.log('this is the setup function for task 2. It should only run once.')
return {
nice: 'yes',
}
},
taskFunction: async (before, setupArg) => {
expect(setupArg).toEqual({ nice: 'yes' });
const done = smartpromise.defer();
console.log('Task2 started');
setTimeout(() => {
console.log('Task2 executed');
done.resolve();
}, 5000);
await done.promise;
},
});
await task2.trigger();
});
tap.test('should not exceed bufferMax when task is buffered', async () => {
let counter = 0;
const bufferedTask = new taskbuffer.Task({
taskFunction: async () => { taskFunction: async () => {
await smartdelay.delayFor(3000); counter++;
await smartdelay.delayFor(2000);
counter--;
}, },
buffered: true, buffered: true,
bufferMax: 2, bufferMax: 2,
}); });
localTestTask.trigger(); bufferedTask.trigger();
localTestTask.trigger(); bufferedTask.trigger();
localTestTask.trigger(); bufferedTask.trigger();
await localTestTask.trigger(); await smartdelay.delayFor(100);
expect(counter <= bufferedTask.bufferMax).toBeTrue();
}); });
tap.start(); tap.start();

View File

@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@push.rocks/taskbuffer', name: '@push.rocks/taskbuffer',
version: '3.0.12', version: '3.0.13',
description: 'flexible task management. TypeScript ready!' description: 'flexible task management. TypeScript ready!'
} }

View File

@ -4,20 +4,24 @@ import { CycleCounter } from './taskbuffer.classes.cyclecounter.js';
import { logger } from './taskbuffer.logging.js'; import { logger } from './taskbuffer.logging.js';
export interface ITaskFunction { export interface ITaskFunction<T = undefined> {
(x?: any): PromiseLike<any>; (x?: any, setupValue?: T): PromiseLike<any>;
} }
export type TPreOrAfterTaskFunction = () => Task; export interface ITaskSetupFunction<T = undefined> {
(): Promise<T>;
}
export class Task { export type TPreOrAfterTaskFunction = () => Task<any>;
export class Task<T = undefined> {
// STATIC // STATIC
public static extractTask(preOrAfterTaskArg: Task | TPreOrAfterTaskFunction): Task { public static extractTask<T = undefined>(preOrAfterTaskArg: Task<T> | TPreOrAfterTaskFunction): Task<T> {
switch (true) { switch (true) {
case !preOrAfterTaskArg: case !preOrAfterTaskArg:
return null; return null;
case preOrAfterTaskArg instanceof Task: case preOrAfterTaskArg instanceof Task:
return preOrAfterTaskArg as Task; return preOrAfterTaskArg as Task<T>;
case typeof preOrAfterTaskArg === 'function': case typeof preOrAfterTaskArg === 'function':
const taskFunction = preOrAfterTaskArg as TPreOrAfterTaskFunction; const taskFunction = preOrAfterTaskArg as TPreOrAfterTaskFunction;
return taskFunction(); return taskFunction();
@ -32,7 +36,7 @@ export class Task {
return done.promise; return done.promise;
}; };
public static isTask = (taskArg: Task): boolean => { public static isTask = (taskArg: Task<any>): boolean => {
if (taskArg instanceof Task && typeof taskArg.taskFunction === 'function') { if (taskArg instanceof Task && typeof taskArg.taskFunction === 'function') {
return true; return true;
} else { } else {
@ -40,10 +44,10 @@ export class Task {
} }
}; };
public static isTaskTouched = ( public static isTaskTouched<T = undefined> (
taskArg: Task | TPreOrAfterTaskFunction, taskArg: Task<T> | TPreOrAfterTaskFunction,
touchedTasksArray: Task[] touchedTasksArray: Task<T>[]
): boolean => { ): boolean {
const taskToCheck = Task.extractTask(taskArg); const taskToCheck = Task.extractTask(taskArg);
let result = false; let result = false;
for (const keyArg in touchedTasksArray) { for (const keyArg in touchedTasksArray) {
@ -54,42 +58,39 @@ export class Task {
return result; return result;
}; };
public static runTask = async ( public static runTask = async <T>(
taskArg: Task | TPreOrAfterTaskFunction, taskArg: Task<T> | TPreOrAfterTaskFunction,
optionsArg: { x?: any; touchedTasksArray?: Task[] } optionsArg: { x?: any; touchedTasksArray?: Task<T>[] }
) => { ) => {
// extracts the task in case it is specified as a return value of a function
const taskToRun = Task.extractTask(taskArg); const taskToRun = Task.extractTask(taskArg);
const done = plugins.smartpromise.defer(); const done = plugins.smartpromise.defer();
// pay respect to execDelay if (!taskToRun.setupValue && taskToRun.taskSetup) {
taskToRun.setupValue = await taskToRun.taskSetup();
}
if (taskToRun.execDelay) { if (taskToRun.execDelay) {
await plugins.smartdelay.delayFor(taskToRun.execDelay); await plugins.smartdelay.delayFor(taskToRun.execDelay);
} }
// set running params
taskToRun.running = true; taskToRun.running = true;
done.promise.then(async () => { done.promise.then(async () => {
taskToRun.running = false; taskToRun.running = false;
}); });
// handle options
const options = { const options = {
...{ x: undefined, touchedTasksArray: [] }, ...{ x: undefined, touchedTasksArray: [] },
...optionsArg, ...optionsArg,
}; };
const x = options.x; const x = options.x;
const touchedTasksArray: Task[] = options.touchedTasksArray; const touchedTasksArray: Task<T>[] = options.touchedTasksArray;
touchedTasksArray.push(taskToRun); touchedTasksArray.push(taskToRun);
// run the task cascade
const localDeferred = plugins.smartpromise.defer(); const localDeferred = plugins.smartpromise.defer();
localDeferred.promise localDeferred.promise
.then(() => { .then(() => {
// lets run any preTask
if (taskToRun.preTask && !Task.isTaskTouched(taskToRun.preTask, touchedTasksArray)) { if (taskToRun.preTask && !Task.isTaskTouched(taskToRun.preTask, touchedTasksArray)) {
return Task.runTask(taskToRun.preTask, { x, touchedTasksArray }); return Task.runTask(taskToRun.preTask, { x, touchedTasksArray });
} else { } else {
@ -99,9 +100,8 @@ export class Task {
} }
}) })
.then(async (x) => { .then(async (x) => {
// lets run the main task
try { try {
return await taskToRun.taskFunction(x); return await taskToRun.taskFunction(x, taskToRun.setupValue);
} catch (e) { } catch (e) {
console.log(e); console.log(e);
} }
@ -126,15 +126,9 @@ export class Task {
}; };
// INSTANCE // INSTANCE
// mandatory properties
public name: string; public name: string;
/**
* the version of the task
* should follow semver
* might be important for DistributedCoordinator
*/
public version: string; public version: string;
public taskFunction: ITaskFunction; public taskFunction: ITaskFunction<T>;
public buffered: boolean; public buffered: boolean;
public cronJob: plugins.smarttime.CronJob; public cronJob: plugins.smarttime.CronJob;
@ -142,11 +136,9 @@ export class Task {
public execDelay: number; public execDelay: number;
public timeout: number; public timeout: number;
// tasks to run before and after public preTask: Task<T> | TPreOrAfterTaskFunction;
public preTask: Task | TPreOrAfterTaskFunction; public afterTask: Task<T> | TPreOrAfterTaskFunction;
public afterTask: Task | TPreOrAfterTaskFunction;
// initialize by default
public running: boolean = false; public running: boolean = false;
public bufferRunner = new BufferRunner(this); public bufferRunner = new BufferRunner(this);
public cycleCounter = new CycleCounter(this); public cycleCounter = new CycleCounter(this);
@ -154,36 +146,18 @@ export class Task {
public idle: boolean = true; public idle: boolean = true;
private _state: string = 'ready'; private _state: string = 'ready';
public taskSetup: ITaskSetupFunction<T>;
public setupValue: T;
constructor(optionsArg: { constructor(optionsArg: {
/** taskFunction: ITaskFunction<T>;
* the task function to run, must return promise preTask?: Task<T> | TPreOrAfterTaskFunction;
*/ afterTask?: Task<T> | TPreOrAfterTaskFunction;
taskFunction: ITaskFunction;
/**
* any other task to run before
*/
preTask?: Task | TPreOrAfterTaskFunction;
/**
* any other task to run after
*/
afterTask?: Task | TPreOrAfterTaskFunction;
/**
* wether this task should run buffered
*/
buffered?: boolean; buffered?: boolean;
/**
* the maximum buffer
*/
bufferMax?: number; bufferMax?: number;
/**
* the execution delay, before the task is executed
* only makes sense when running in buffered mode
*/
execDelay?: number; execDelay?: number;
/**
* the name of the task
*/
name?: string; name?: string;
taskSetup?: ITaskSetupFunction<T>;
}) { }) {
this.taskFunction = optionsArg.taskFunction; this.taskFunction = optionsArg.taskFunction;
this.preTask = optionsArg.preTask; this.preTask = optionsArg.preTask;
@ -193,11 +167,9 @@ export class Task {
this.bufferMax = optionsArg.bufferMax; this.bufferMax = optionsArg.bufferMax;
this.execDelay = optionsArg.execDelay; this.execDelay = optionsArg.execDelay;
this.name = optionsArg.name; this.name = optionsArg.name;
this.taskSetup = optionsArg.taskSetup;
} }
/**
* trigger the task. Will trigger buffered if this.buffered is true
*/
public trigger(x?: any): Promise<any> { public trigger(x?: any): Promise<any> {
if (this.buffered) { if (this.buffered) {
return this.triggerBuffered(x); return this.triggerBuffered(x);
@ -206,18 +178,10 @@ export class Task {
} }
} }
/**
* trigger task unbuffered.
* will actually run the task, not considering any buffered limits.
*/
public triggerUnBuffered(x?: any): Promise<any> { public triggerUnBuffered(x?: any): Promise<any> {
return Task.runTask(this, { x: x }); return Task.runTask<T>(this, { x: x });
} }
/**
* trigger task buffered.
* note: .trigger() also calls this function
*/
public triggerBuffered(x?: any): Promise<any> { public triggerBuffered(x?: any): Promise<any> {
return this.bufferRunner.trigger(x); return this.bufferRunner.trigger(x);
} }