Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: improve methods chaining compatibility #2

Merged
merged 1 commit into from
Sep 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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