BREAKING CHANGE(taskbuffer): Change default Task error handling: trigger() now rejects when taskFunction throws; add catchErrors option (default false) to preserve previous swallow behavior; track errors (lastError, errorCount) and expose them in metadata; improve error propagation and logging across runners, chains, parallels and debounced tasks; add tests and documentation for new behavior.

This commit is contained in:
2026-01-25 23:29:00 +00:00
parent 905ca97b6a
commit 248383aab1
16 changed files with 575 additions and 63 deletions

View File

@@ -87,24 +87,37 @@ export class Task<T = undefined, TSteps extends ReadonlyArray<{ name: string; de
taskToRun.running = true;
taskToRun.runCount++;
taskToRun.lastRun = new Date();
// Reset steps at the beginning of task execution
// Reset steps and error state at the beginning of task execution
taskToRun.resetSteps();
taskToRun.lastError = undefined;
done.promise.then(async () => {
taskToRun.running = false;
// Complete all steps when task finishes
taskToRun.completeAllSteps();
done.promise
.then(async () => {
taskToRun.running = false;
// When the task has finished running, resolve the finished promise
taskToRun.resolveFinished();
// Complete all steps when task finishes
taskToRun.completeAllSteps();
// Create a new finished promise for the next run
taskToRun.finished = new Promise((resolve) => {
taskToRun.resolveFinished = resolve;
// When the task has finished running, resolve the finished promise
taskToRun.resolveFinished();
// Create a new finished promise for the next run
taskToRun.finished = new Promise((resolve) => {
taskToRun.resolveFinished = resolve;
});
})
.catch((err) => {
taskToRun.running = false;
// Resolve finished so blocking dependants don't hang
taskToRun.resolveFinished();
// Create a new finished promise for the next run
taskToRun.finished = new Promise((resolve) => {
taskToRun.resolveFinished = resolve;
});
});
});
const options = {
...{ x: undefined, touchedTasksArray: [] },
@@ -133,7 +146,13 @@ export class Task<T = undefined, TSteps extends ReadonlyArray<{ name: string; de
try {
return await taskToRun.taskFunction(x, taskToRun.setupValue);
} catch (e) {
console.log(e);
taskToRun.lastError = e instanceof Error ? e : new Error(String(e));
taskToRun.errorCount++;
logger.log('error', `Task "${taskToRun.name || 'unnamed'}" failed: ${taskToRun.lastError.message}`);
if (taskToRun.catchErrors) {
return undefined;
}
throw e;
}
})
.then((x) => {
@@ -155,10 +174,18 @@ export class Task<T = undefined, TSteps extends ReadonlyArray<{ name: string; de
done.resolve(x);
})
.catch((err) => {
console.log(err);
done.reject(err);
});
localDeferred.resolve();
return await done.promise;
try {
return await done.promise;
} catch (err) {
if (taskToRun.catchErrors) {
return undefined;
}
throw err;
}
};
public name: string;
@@ -187,10 +214,19 @@ export class Task<T = undefined, TSteps extends ReadonlyArray<{ name: string; de
public lastRun?: Date;
public runCount: number = 0;
// Error handling
public catchErrors: boolean = false;
public lastError?: Error;
public errorCount: number = 0;
public get idle() {
return !this.running;
}
public clearError(): void {
this.lastError = undefined;
}
public taskSetup: ITaskSetupFunction<T>;
public setupValue: T;
@@ -210,6 +246,7 @@ export class Task<T = undefined, TSteps extends ReadonlyArray<{ name: string; de
name?: string;
taskSetup?: ITaskSetupFunction<T>;
steps?: TSteps;
catchErrors?: boolean;
}) {
this.taskFunction = optionsArg.taskFunction;
this.preTask = optionsArg.preTask;
@@ -219,6 +256,7 @@ export class Task<T = undefined, TSteps extends ReadonlyArray<{ name: string; de
this.execDelay = optionsArg.execDelay;
this.name = optionsArg.name;
this.taskSetup = optionsArg.taskSetup;
this.catchErrors = optionsArg.catchErrors ?? false;
// Initialize steps if provided
if (optionsArg.steps) {
@@ -306,10 +344,21 @@ export class Task<T = undefined, TSteps extends ReadonlyArray<{ name: string; de
// Get task metadata
public getMetadata(): ITaskMetadata {
let status: 'idle' | 'running' | 'completed' | 'failed';
if (this.running) {
status = 'running';
} else if (this.lastError) {
status = 'failed';
} else if (this.runCount > 0) {
status = 'completed';
} else {
status = 'idle';
}
return {
name: this.name || 'unnamed',
version: this.version,
status: this.running ? 'running' : 'idle',
status,
steps: this.getStepsMetadata(),
currentStep: this.currentStepName,
currentProgress: this.getProgress(),
@@ -318,6 +367,8 @@ export class Task<T = undefined, TSteps extends ReadonlyArray<{ name: string; de
bufferMax: this.bufferMax,
timeout: this.timeout,
cronSchedule: this.cronJob?.cronExpression,
lastError: this.lastError?.message,
errorCount: this.errorCount,
};
}