From 25636d2db736bd5182a09904a4a8a519802604af Mon Sep 17 00:00:00 2001 From: Christopher Hiller Date: Mon, 26 Aug 2024 14:01:14 -0700 Subject: [PATCH] feat(trampoline): add proper error handling --- packages/trampoline/src/trampoline.js | 16 +++-- packages/trampoline/test/trampoline.test.js | 77 ++++++++++++++++----- 2 files changed, 70 insertions(+), 23 deletions(-) diff --git a/packages/trampoline/src/trampoline.js b/packages/trampoline/src/trampoline.js index 98a6b8ab1c..228e95312d 100644 --- a/packages/trampoline/src/trampoline.js +++ b/packages/trampoline/src/trampoline.js @@ -17,7 +17,11 @@ export function syncTrampoline(generatorFn, ...args) { const iterator = generatorFn(...args); let result = iterator.next(); while (!result.done) { - result = iterator.next(result.value); + try { + result = iterator.next(result.value); + } catch (err) { + result = iterator.throw(err); + } } return result.value; } @@ -36,9 +40,13 @@ export async function trampoline(generatorFn, ...args) { const iterator = generatorFn(...args); let result = iterator.next(); while (!result.done) { - // eslint-disable-next-line no-await-in-loop - const val = await result.value; - result = iterator.next(val); + try { + // eslint-disable-next-line no-await-in-loop + const val = await result.value; + result = iterator.next(val); + } catch (err) { + result = iterator.throw(err); + } } return result.value; } diff --git a/packages/trampoline/test/trampoline.test.js b/packages/trampoline/test/trampoline.test.js index 98fe698606..6a5aa22b8a 100644 --- a/packages/trampoline/test/trampoline.test.js +++ b/packages/trampoline/test/trampoline.test.js @@ -33,12 +33,12 @@ function* operationsWithThunk(thunk, input = 0) { */ function* operations(thunk, input = 0) { let result = input * 2; // First operation - result = yield thunk(result); // Call the hook, which can be sync or async - result *= 2; // Operation on the hook's result - // Check if the result is divisible by N, and if so, recurse - while (result < 1000) { - result *= 2; + try { + result = yield thunk(result); // Call the hook, which can be sync or async + } catch { + return result; } + result *= 2; // Operation on the hook's result return result; } @@ -61,35 +61,74 @@ async function asyncThunk(x = 0) { return x + 10; } -const expectedRecursionResult = 2980; -const expectedResult = 1280; +/** + * Asynchronous thunk which throws + * @param {number} _x + * @returns {Promise} + */ +async function brokenAsyncThunk(_x = 0) { + throw new Error('insubordinate!'); +} + +// eslint-disable-next-line jsdoc/require-returns-check +/** + * Synchronous thunk which throws + * @param {number} _x + * @returns {number} + */ +function brokenSyncThunk(_x = 0) { + throw new Error('churlish!'); +} + +/** + * IDK; it does what it does. + */ +const expectedRecursiveResult = 2980; +/** + * Should be (2 * initial value + 10) * 2 + */ +const expectedResult = 40; +/** + * Should be 2 * initial value + */ +const expectedErrorResult = 10; test('synchronous execution - recursion', t => { - const syncResult = syncTrampoline(operationsWithThunk, syncThunk, 5); - t.is(syncResult, expectedRecursionResult); + const actual = syncTrampoline(operationsWithThunk, syncThunk, 5); + t.is(actual, expectedRecursiveResult); }); test('asynchronous execution w/ sync thunk - recursion', async t => { - const asyncResult = await trampoline(operationsWithThunk, syncThunk, 5); - t.is(asyncResult, expectedRecursionResult); + const actual = await trampoline(operationsWithThunk, syncThunk, 5); + t.is(actual, expectedRecursiveResult); }); test('asynchronous execution - recursion', async t => { - const asyncResult = await trampoline(operationsWithThunk, asyncThunk, 5); - t.is(asyncResult, expectedRecursionResult); + const actual = await trampoline(operationsWithThunk, asyncThunk, 5); + t.is(actual, expectedRecursiveResult); }); test('synchronous execution', t => { - const syncResult = syncTrampoline(operations, syncThunk, 5); - t.is(syncResult, expectedResult); + const actual = syncTrampoline(operations, syncThunk, 5); + t.is(actual, expectedResult); }); test('asynchronous execution w/ sync thunk', async t => { - const asyncResult = await trampoline(operations, syncThunk, 5); - t.is(asyncResult, expectedResult); + const actual = await trampoline(operations, syncThunk, 5); + t.is(actual, expectedResult); }); test('asynchronous execution', async t => { - const asyncResult = await trampoline(operations, syncThunk, 5); - t.is(asyncResult, expectedResult); + const actual = await trampoline(operations, syncThunk, 5); + t.is(actual, expectedResult); +}); + +test('async error handling', async t => { + const actual = await trampoline(operations, brokenAsyncThunk, 5); + t.is(actual, expectedErrorResult); +}); + +test('sync error handling', t => { + const actual = syncTrampoline(operations, brokenSyncThunk, 5); + t.is(actual, expectedErrorResult); });