diff --git a/src/types.ts b/src/types.ts index 89c0b304..160a801a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -256,7 +256,7 @@ export function createOptionalCallbackFunction( if (isErrorFirstCallback(possibleCallback)) { try { const result = syncVersion(...(args.slice(0, -1) as A)); - possibleCallback(null, result); + process.nextTick(() => possibleCallback(null, result)); } catch (err) { possibleCallback(err instanceof Error ? err : new Error("Unknown error")); } diff --git a/test/types-tests.spec.ts b/test/types-tests.spec.ts new file mode 100644 index 00000000..4bcbb7c6 --- /dev/null +++ b/test/types-tests.spec.ts @@ -0,0 +1,52 @@ +/// +import * as types from "../src/types"; +import { expect } from "chai"; + +describe("createOptionalCallbackFunction", function () { + it("should not execute callback twice when callback throws unhandled exception", function (done) { + const syncFn = (a: number, b: number) => a + b; + const flexibleFn = types.createOptionalCallbackFunction(syncFn); + + let callbackExecutionCount = 0; + + // Store and remove existing unhandled exception listeners + const existingListeners = process.rawListeners("uncaughtException"); + process.removeAllListeners("uncaughtException"); + + process.once("uncaughtException", (err) => { + // Restore unhandled exception listeners + existingListeners.forEach((listener) => { + process.on("uncaughtException", listener as NodeJS.UncaughtExceptionListener); + }); + + expect(err.message).to.equal("Callback threw an error"); + expect(callbackExecutionCount).to.equal(1); + done(); + }); + + flexibleFn(2, 3, (err, result) => { + callbackExecutionCount++; + expect(err).to.be.null; + expect(result).to.equal(5); + + throw new Error("Callback threw an error"); + }); + }); + + it("should defer callback execution in success case", function (done) { + const syncFn = (a: number, b: number) => a + b; + const flexibleFn = types.createOptionalCallbackFunction(syncFn); + + let callbackExecuted = false; + + flexibleFn(2, 3, (err, result) => { + callbackExecuted = true; + expect(err).to.be.null; + expect(result).to.equal(5); + done(); + }); + + // Callback should be asynchronously deferred + expect(callbackExecuted).to.be.false; + }); +});