Skip to content

Commit

Permalink
fix: improve methods chaining compatibility (#2)
Browse files Browse the repository at this point in the history
  • Loading branch information
kettanaito committed Sep 26, 2022
1 parent 1bf7eed commit 05e7e93
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 31 deletions.
27 changes: 16 additions & 11 deletions src/DeferredPromise.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
export type DeferredPromiseState = "pending" | "resolved" | "rejected";
export type ResolveFunction<Data extends any, Result = void> = (
data: Data
) => Result;
export type RejectFunction<Result = void> = (reason?: unknown) => Result;
) => Result | PromiseLike<Result>;
export type RejectFunction<Result = void> = (
reason?: unknown
) => Result | PromiseLike<Result>;

/**
* Represents the completion of an asynchronous operation.
Expand All @@ -16,7 +18,7 @@ export type RejectFunction<Result = void> = (reason?: unknown) => Result;
* const portReady = new DeferredPromise()
* portReady.reject(new Error('Port is already in use'))
*/
export class DeferredPromise<Data extends unknown = void> {
export class DeferredPromise<Data extends any = void> {
public resolve: ResolveFunction<Data>;
public reject: RejectFunction;
public state: DeferredPromiseState;
Expand Down Expand Up @@ -60,20 +62,23 @@ export class DeferredPromise<Data extends unknown = void> {
/**
* Attaches callbacks for the resolution and/or rejection of the Promise.
*/
public then(
onresolved?: ResolveFunction<Data, any>,
onrejected?: RejectFunction
) {
this.promise = this.promise.then(onresolved, onrejected);
return this;
public then<ResolveData = Data, RejectionReason = never>(
onresolved?: ResolveFunction<Data, ResolveData>,
onrejected?: RejectFunction<RejectionReason>
): DeferredPromise<ResolveData | RejectionReason> {
this.promise = this.promise.then<ResolveData, RejectionReason>(
onresolved,
onrejected
);
return this as DeferredPromise<ResolveData | RejectionReason>;
}

/**
* Attaches a callback for only the rejection of the Promise.
*/
public catch<RejectReason = never>(
onrejected?: RejectFunction<RejectReason>
): this {
): DeferredPromise<Data | RejectReason> {
this.promise = this.promise.catch<RejectReason>(onrejected);
return this;
}
Expand All @@ -83,7 +88,7 @@ export class DeferredPromise<Data extends unknown = void> {
* the Promise is settled (fulfilled or rejected). The resolved
* value cannot be modified from the callback.
*/
public finally(onfinally?: () => void): this {
public finally(onfinally?: () => void): DeferredPromise<Data> {
this.promise = this.promise.finally(onfinally);
return this;
}
Expand Down
86 changes: 66 additions & 20 deletions test/DeferredPromise.test.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,82 @@
import { DeferredPromise } from "../src";

it('can be listened to with ".then()"', (done) => {
expect.assertions(1);
describe("Promise-compliance", () => {
it('can be listened to with ".then()"', (done) => {
expect.assertions(1);

const promise = new DeferredPromise<number>();
const promise = new DeferredPromise<number>();

promise.then((data) => {
expect(data).toBe(123);
done();
});

promise.resolve(123);
});

it('can be listened to with ".catch()"', (done) => {
expect.assertions(1);

const promise = new DeferredPromise<number>();
promise.catch((reason) => {
expect(reason).toBe("error");
done();
});

promise.then((data) => {
promise.reject("error");
});

it("can be awaited with async/await", async () => {
const promise = new DeferredPromise<number>();
promise.resolve(123);

const data = await promise;
expect(data).toBe(123);
done();
});

promise.resolve(123);
});
it('allows data transformation in the ".then()" chain', async () => {
const promise = new DeferredPromise<number>();

it('can be listened to with ".catch()"', (done) => {
expect.assertions(1);
promise.then((value) => value * 2).then((value) => value + 10);
promise.resolve(5);

const promise = new DeferredPromise<number>();
promise.catch((reason) => {
expect(reason).toBe("error");
done();
const number = await promise;

expect(number).toBe(20);
});

promise.reject("error");
});
it('allows ".catch().then()" chaining', async () => {
const promise = new DeferredPromise<number>();

promise
.catch<number>((value) => {
if (typeof value === "number") {
return value;
}
})
.then((value) => value + 10);

promise.reject(5);
const number = await promise;

expect(number).toBe(15);
});

it("can be awaited", async () => {
const promise = new DeferredPromise<number>();
promise.resolve(123);
it('does not alter resolved data with ".finally()"', async () => {
const promise = new DeferredPromise<number>();

const finallyCallback = jest.fn(() => "unexpected");
const wrapper = (): Promise<number> => {
return promise.finally(finallyCallback);
};

promise.resolve(123);
const result = await wrapper();

const data = await promise;
expect(data).toBe(123);
expect(result).toBe(123);
expect(finallyCallback).toHaveBeenCalledTimes(1);
expect(finallyCallback).toHaveBeenCalledWith();
});
});

describe("resolve()", () => {
Expand Down

0 comments on commit 05e7e93

Please sign in to comment.