Skip to content

Commit

Permalink
on error called before decrement counter
Browse files Browse the repository at this point in the history
  • Loading branch information
Francesco Rivola committed Nov 6, 2023
1 parent b38b13a commit 3053ba8
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 70 deletions.
130 changes: 69 additions & 61 deletions src/fire-and-forgetter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,20 @@ import ClosingError from "./errors/closing-error";
import TimeoutClosingError from "./errors/timeout-closing-error";

type Options = {
defaultOnError: (error: Error) => void;
defaultOnError: (error: Error) => void;
};

type FireAndForgetter = {
close: (options?: CloseOptions) => Promise<void>;
close: (options?: CloseOptions) => Promise<void>;
} & ((func: () => Promise<void>, onError?: (error: Error) => void) => void);

type CloseOptions = {
timeout: number;
}
timeout: number;
};

const defaultOptions: Options = {
defaultOnError: (error) => console.error(error),
}
defaultOnError: (error) => console.error(error),
};

/**
* Get a new instance of the fire and forgetter lib.
Expand All @@ -27,64 +27,72 @@ const defaultOptions: Options = {
* }]
* @returns a fire and forgetter object instance.
*/
export function fireAndForgetter(options: Options = defaultOptions): FireAndForgetter {

const counter = createCounter();
let closing = false;
export function fireAndForgetter(
options: Options = defaultOptions
): FireAndForgetter {
const counter = createCounter();
let closing = false;

async function executeFireAndForget(func: () => Promise<void>): Promise<void> {
try {
counter.incrementCounter();
await func();
} finally {
counter.decrementCounter();
}
/**
* Execute a function in fire and forget mode.
*
* @param {() => Promise<void>} func function executed in fire and forget mode. It must return a promise.
* @param {(error: Error) => void} [onError=options.defaultOnError] error callback to handle function rejection.
* @throws {ClosingError} when close function is called this error will be thrown.
*/
function fireAndForget(
func: () => Promise<void>,
onError: (error: Error) => void = options.defaultOnError
): void {
if (closing) {
throw new ClosingError(
"Cannot longer execute fire and forget operation as is closing or closed"
);
}
counter.incrementCounter();
func()
.catch(onError)
.finally(() => counter.decrementCounter());
}

/**
* Execute a function in fire and forget mode.
*
* @param {() => Promise<void>} func function executed in fire and forget mode. It must return a promise.
* @param {(error: Error) => void} [onError=options.defaultOnError] error callback to handle function rejection.
* @throws {ClosingError} when close function is called this error will be thrown.
*/
function fireAndForget(func: () => Promise<void>, onError: (error: Error) => void = options.defaultOnError): void {
if (closing) {
throw new ClosingError("Cannot longer execute fire and forget operation as is closing or closed");
/**
* close the fire and forgetter instance.
* The function will return a promise that will resolve once all fire and forget operations are done.
* Also, any new fire and forget function requested will throw a ClosingError.
*
* @param {{ timeout: number }} [closeOptions={ timeout: 0 }] if timeout is > 0 the function
* will throw a TimeoutClosingError if fire and forget operations do not complete before the set timeout.
* default timeout value is 0, means no timeout.
* @returns {Promise<void>}
* @throws {TimeoutClosingError} when fire and forget operations do not complete before the set timeout.
*/
function close(closeOptions: CloseOptions = { timeout: 0 }): Promise<void> {
closing = true;
return new Promise<void>((resolve, reject) => {
if (counter.getCount() === 0) {
resolve();
return;
}
counter.registerSubscriberToCounterChanges((count) => {
if (count === 0) {
resolve();
}
executeFireAndForget(func).catch(onError);
}

/**
* close the fire and forgetter instance.
* The function will return a promise that will resolve once all fire and forget operations are done.
* Also, any new fire and forget function requested will throw a ClosingError.
*
* @param {{ timeout: number }} [closeOptions={ timeout: 0 }] if timeout is > 0 the function
* will throw a TimeoutClosingError if fire and forget operations do not complete before the set timeout.
* default timeout value is 0, means no timeout.
* @returns {Promise<void>}
* @throws {TimeoutClosingError} when fire and forget operations do not complete before the set timeout.
*/
function close(closeOptions: CloseOptions = { timeout: 0 }): Promise<void> {
closing = true;
return new Promise<void>((resolve, reject) => {
if (counter.getCount() === 0) {
resolve();
return;
}
counter.registerSubscriberToCounterChanges((count) => {
if (count === 0) {
resolve();
}
});
const { timeout } = closeOptions;
if (timeout > 0) {
setTimeout(() => reject(new TimeoutClosingError(`Cannot close after ${timeout}ms, ${counter.getCount()} fire and forget operations are still in progress`)), timeout);
}
});
}
fireAndForget.close = close;
});
const { timeout } = closeOptions;
if (timeout > 0) {
setTimeout(
() =>
reject(
new TimeoutClosingError(
`Cannot close after ${timeout}ms, ${counter.getCount()} fire and forget operations are still in progress`
)
),
timeout
);
}
});
}
fireAndForget.close = close;

return fireAndForget;
return fireAndForget;
}
27 changes: 18 additions & 9 deletions test/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,7 @@ describe("fire-and-forgetter", () => {
return Promise.resolve();
}

async function doSomeStuffsAndIncrementCountAtTheEndAndReject(): Promise<
void
> {
async function doSomeStuffsAndIncrementCountAtTheEndAndReject(): Promise<void> {
await setTimeout(10);
count++;
return Promise.reject(new Error("ups, some error happened"));
Expand All @@ -39,7 +37,7 @@ describe("fire-and-forgetter", () => {

test("close should throw a timeout closing error if timeout is reached and fire and forget operation are still in process", async () => {
const fireAndForget = fireAndForgetter();

let functionHasThrownError = false;
let count = 0;

async function doSomeStuffsAndIncrementCountAtTheEnd(): Promise<void> {
Expand All @@ -62,7 +60,9 @@ describe("fire-and-forgetter", () => {
"Cannot close after 10ms, 3 fire and forget operations are still in progress"
);
equal(count, 0);
functionHasThrownError = true;
}
equal(functionHasThrownError, true);
});

test("close should resolve when no fire and forget operations are in process", async () => {
Expand All @@ -71,30 +71,34 @@ describe("fire-and-forgetter", () => {
});

test("fireAndForget should call onError callback when operation rejects", async () => {
let onErrorHasBeenCalled = false;
const fireAndForget = fireAndForgetter();

async function doSomeStuffsAndReject(): Promise<void> {
await setTimeout(10);
return Promise.reject(new Error("ups, some error happened"));
}

fireAndForget(
() => doSomeStuffsAndReject(),
error => {
(error) => {
equal(error instanceof Error, true);
equal((error as Error).message, "ups, some error happened");
onErrorHasBeenCalled = true;
}
);

await fireAndForget.close();
equal(onErrorHasBeenCalled, true);
});

test("fireAndForget should call defaultOnError callback when operation rejects and no onError callback is set", async () => {
let defaultOnErrorHasBeenCalled = false;
const fireAndForget = fireAndForgetter({
defaultOnError: error => {
defaultOnError: (error) => {
equal(error instanceof Error, true);
equal(error.message, "ups, some error happened");
}
defaultOnErrorHasBeenCalled = true;
},
});

async function doSomeStuffsAndReject(): Promise<void> {
Expand All @@ -105,9 +109,12 @@ describe("fire-and-forgetter", () => {
fireAndForget(() => doSomeStuffsAndReject());

await fireAndForget.close();

equal(defaultOnErrorHasBeenCalled, true);
});

test("fireAndForget should throw a closing error if fire and forget has been closed", async () => {
let functionHasThrownError = false;
const fireAndForget = fireAndForgetter();

async function doSomeStuffsAndIncrementCountAtTheEnd(): Promise<void> {
Expand All @@ -121,13 +128,15 @@ describe("fire-and-forgetter", () => {

try {
fireAndForget(() => doSomeStuffsAndIncrementCountAtTheEnd());
throw new Error("It should not get here");
} catch (error) {
equal(error instanceof ClosingError, true);
equal(
(error as Error).message,
"Cannot longer execute fire and forget operation as is closing or closed"
);
functionHasThrownError = true;
}

equal(functionHasThrownError, true);
});
});

0 comments on commit 3053ba8

Please sign in to comment.