Skip to content

Commit

Permalink
Merge 715a8a7 into a2cf595
Browse files Browse the repository at this point in the history
  • Loading branch information
myty committed Sep 24, 2022
2 parents a2cf595 + 715a8a7 commit 2fc068e
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 18 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
8 changes: 4 additions & 4 deletions promise-chain.bench.ts
Original file line number Diff line number Diff line change
@@ -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 = (
Expand All @@ -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(
Expand All @@ -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,
)
Expand All @@ -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()
Expand Down
50 changes: 46 additions & 4 deletions promise-chain.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import {
assert,
assertEquals,
assertRejects,
} from "https://deno.land/std@0.154.0/testing/asserts.ts";
import { PromiseChain } from "./promise-chain.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() {
Expand All @@ -28,7 +34,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()
Expand All @@ -46,7 +52,7 @@ Deno.test(function whenComposableAsyncItIsPromise() {
const testClass = new TestClass();

// Act
const result = PromiseChain.create(testClass);
const result = chain(testClass);

// Assert
assert(result instanceof PromiseChain);
Expand All @@ -57,7 +63,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;
Expand All @@ -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);
});
17 changes: 8 additions & 9 deletions promise-chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<T> extends Promise<T> implements Promise<T> {
/**
* Create a chaninable class based off of the functions that return "this" or a Promise of "this".
*/
static create<T>(wrappedClass: T): AsyncComposable<T> {
return new PromiseChain(wrappedClass) as unknown as AsyncComposable<T>;
}

export default class PromiseChain<T> extends Promise<T> implements Promise<T> {
private _valuePromise: Promise<T>;

private constructor(_wrappedClass: T) {
constructor(_wrappedClass: T) {
super((_resolve, _reject) => {});

this._valuePromise = Promise.resolve(_wrappedClass);
Expand Down Expand Up @@ -55,3 +48,9 @@ export class PromiseChain<T> extends Promise<T> implements Promise<T> {
return keys as Array<keyof T>;
}
}

export function chain<T>(wrappedObj: T) {
return new PromiseChain(wrappedObj) as unknown as
& PromiseChain<T>
& AsyncComposable<T>;
}
5 changes: 5 additions & 0 deletions stubs/test-class-with-exceptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export class TestClassWithException {
throwException(): Promise<TestClassWithException> {
return Promise.reject();
}
}

0 comments on commit 2fc068e

Please sign in to comment.