From cec2dadb67c48a45a74c83064846435b53b35c2e Mon Sep 17 00:00:00 2001 From: Michael Tyson Date: Sat, 24 Sep 2022 13:59:32 -0400 Subject: [PATCH 1/4] extract create and rename to chain --- promise-chain.bench.ts | 8 ++++---- promise-chain.test.ts | 8 ++++---- promise-chain.ts | 17 ++++++++--------- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/promise-chain.bench.ts b/promise-chain.bench.ts index 9e15c68..8f140aa 100644 --- a/promise-chain.bench.ts +++ b/promise-chain.bench.ts @@ -1,4 +1,4 @@ -import { PromiseChain } from "./promise-chain.ts"; +import { chain } from "./promise-chain.ts"; import { TestClass } from "./stubs/test-class.ts"; const iterate = ( @@ -18,7 +18,7 @@ Deno.bench( Deno.bench( "Composable Async Chain (1 Step)", { group: "1 step" }, - iterate((t) => PromiseChain.create(t).asyncIncrement("propertyOne", 3)), + iterate((t) => chain(t).asyncIncrement("propertyOne", 3)), ); Deno.bench( @@ -35,7 +35,7 @@ Deno.bench( "Composable Async Chain (2 Steps)", { group: "2 steps" }, iterate((t) => - PromiseChain.create(t).asyncIncrement("propertyOne", 3).increment( + chain(t).asyncIncrement("propertyOne", 3).increment( "propertyTwo", 5, ) @@ -60,7 +60,7 @@ Deno.bench( "Composable Async Chain (6 Steps)", { group: "6 steps" }, iterate((t) => - PromiseChain.create(t) + chain(t) .asyncIncrement("propertyOne", 3) .asyncIncrementTwo() .asyncIncrementOne() diff --git a/promise-chain.test.ts b/promise-chain.test.ts index b567018..ef26f47 100644 --- a/promise-chain.test.ts +++ b/promise-chain.test.ts @@ -2,7 +2,7 @@ import { assert, assertEquals, } from "https://deno.land/std@0.154.0/testing/asserts.ts"; -import { PromiseChain } from "./promise-chain.ts"; +import PromiseChain, { chain } from "./promise-chain.ts"; import { TestClass } from "./stubs/test-class.ts"; Deno.test(async function whenTraditionalAsyncChainingItReturnsResult() { @@ -28,7 +28,7 @@ Deno.test(async function whenAsyncChainingItReturnsResult() { const testClass = new TestClass(); // Act - const result = await PromiseChain.create(testClass) + const result = await chain(testClass) .asyncIncrement("propertyOne", 3) .asyncIncrementTwo() .asyncIncrementOne() @@ -46,7 +46,7 @@ Deno.test(function whenComposableAsyncItIsPromise() { const testClass = new TestClass(); // Act - const result = PromiseChain.create(testClass); + const result = chain(testClass); // Assert assert(result instanceof PromiseChain); @@ -57,7 +57,7 @@ Deno.test(async function whenChainedPromiseIsReusedItReturnsCachedResult() { // Arrange const testClass = new TestClass(); const durationExpectedMs = 250; - const resultTask = PromiseChain.create(testClass) + const resultTask = chain(testClass) .asyncIncrement("propertyTwo", 3) .asyncIncrementOneLongRunningTask(durationExpectedMs); await resultTask; diff --git a/promise-chain.ts b/promise-chain.ts index c446d4a..40baa6f 100644 --- a/promise-chain.ts +++ b/promise-chain.ts @@ -4,17 +4,10 @@ import { AsyncComposable } from "./types.ts"; * Utility class to wrap a composition class with the intended purpose of chaining methods, specifically useful for * functions that return Promises. Note: Promise functions and non-promise functions can be mixed. */ -export class PromiseChain extends Promise implements Promise { - /** - * Create a chaninable class based off of the functions that return "this" or a Promise of "this". - */ - static create(wrappedClass: T): AsyncComposable { - return new PromiseChain(wrappedClass) as unknown as AsyncComposable; - } - +export default class PromiseChain extends Promise implements Promise { private _valuePromise: Promise; - private constructor(_wrappedClass: T) { + constructor(_wrappedClass: T) { super((_resolve, _reject) => {}); this._valuePromise = Promise.resolve(_wrappedClass); @@ -55,3 +48,9 @@ export class PromiseChain extends Promise implements Promise { return keys as Array; } } + +export function chain(wrappedObj: T) { + return new PromiseChain(wrappedObj) as unknown as + & PromiseChain + & AsyncComposable; +} From 098056c22fbcfe7c98812733caf447161752f762 Mon Sep 17 00:00:00 2001 From: Michael Tyson Date: Sat, 24 Sep 2022 14:00:19 -0400 Subject: [PATCH 2/4] update README sample code --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 46d67f5..2476ada 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ console.log(`Result: propertyOne=${propertyOne}, propertyTwo=${propertyTwo}`); With PromiseChain, it is simplified and easier to read. ```typescript -const { propertyOne, propertyTwo } = await Composable.create(testClass) +const { propertyOne, propertyTwo } = await chain(testClass) .asyncIncrement("propertyOne", 3) .asyncIncrement("propertyTwo", 5) .increment("propertyTwo", 5); From 715a8a7cabc3482441cf23175dabdeea407511c5 Mon Sep 17 00:00:00 2001 From: Michael Tyson Date: Sat, 24 Sep 2022 14:18:12 -0400 Subject: [PATCH 3/4] update tests --- promise-chain.test.ts | 42 +++++++++++++++++++++++++++++ stubs/test-class-with-exceptions.ts | 5 ++++ 2 files changed, 47 insertions(+) create mode 100644 stubs/test-class-with-exceptions.ts diff --git a/promise-chain.test.ts b/promise-chain.test.ts index ef26f47..b7ddd38 100644 --- a/promise-chain.test.ts +++ b/promise-chain.test.ts @@ -1,8 +1,14 @@ import { assert, assertEquals, + assertRejects, } from "https://deno.land/std@0.154.0/testing/asserts.ts"; +import { + assertSpyCalls, + spy, +} from "https://deno.land/std@0.157.0/testing/mock.ts"; import PromiseChain, { chain } from "./promise-chain.ts"; +import { TestClassWithException } from "./stubs/test-class-with-exceptions.ts"; import { TestClass } from "./stubs/test-class.ts"; Deno.test(async function whenTraditionalAsyncChainingItReturnsResult() { @@ -76,3 +82,39 @@ Deno.test(async function whenChainedPromiseIsReusedItReturnsCachedResult() { assertEquals(resultTwo.propertyOne, 1); assertEquals(resultTwo.propertyTwo, 6); }); + +Deno.test(function whenPromiseChainHasExceptionItIsRejected() { + // Arrange + const testClassWithException = new TestClassWithException(); + + // Act, Assert + assertRejects(() => chain(testClassWithException).throwException()); +}); + +Deno.test(async function whenPromiseChainHasExceptionItIsCaught() { + // Arrange + const catchSpy = spy(); + const testClassWithException = new TestClassWithException(); + + // Act + await chain(testClassWithException) + .throwException() + .catch(catchSpy); + + // Assert + assertSpyCalls(catchSpy, 1); +}); + +Deno.test(async function whenPromiseChainPromiseIsFinalized() { + // Arrange + const finallySpy = spy(); + const testClass = new TestClass(); + + // Act + await chain(testClass) + .asyncIncrementOne() + .finally(finallySpy); + + // Assert + assertSpyCalls(finallySpy, 1); +}); diff --git a/stubs/test-class-with-exceptions.ts b/stubs/test-class-with-exceptions.ts new file mode 100644 index 0000000..4ffa312 --- /dev/null +++ b/stubs/test-class-with-exceptions.ts @@ -0,0 +1,5 @@ +export class TestClassWithException { + throwException(): Promise { + return Promise.reject(); + } +} From de59b8217f9c80ab2c811da6836cc0344adaad1c Mon Sep 17 00:00:00 2001 From: Michael Tyson Date: Sat, 24 Sep 2022 14:20:49 -0400 Subject: [PATCH 4/4] update mod --- mod.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mod.ts b/mod.ts index ddeb41d..8d8e3d2 100644 --- a/mod.ts +++ b/mod.ts @@ -1 +1,3 @@ -export { PromiseChain } from "./promise-chain.ts"; +import PromiseChain, { chain } from "./promise-chain.ts"; + +export { chain, PromiseChain };