Skip to content

Commit 8d6fd8d

Browse files
committed
docs: add readme
1 parent f00f2bd commit 8d6fd8d

File tree

3 files changed

+135
-15
lines changed

3 files changed

+135
-15
lines changed

README.md

+114
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
# Deferred Promise
2+
3+
The `DeferredPromise` class is a Promise-compatible abstraction that defers resolving/rejecting promises to another closure. This class is primarily useful when one part of your system establishes as promise but another part of your system fulfills it.
4+
5+
> This class is conceptually inspired by the [`createDeferredPromise()`](https://github.com/nodejs/node/blob/696fd4b14fc34cc2d01497a3abd9bb441b89be50/lib/internal/util.js#L468-L477) internal utility in Node.js. Unlike the Node.js implementation, however, `DeferredProimse` _extends_ a native `Promise`, allowing the consumer to handle deferred promises like regular promises (no `.promise` instance nesting).
6+
7+
## Getting started
8+
9+
```sh
10+
npm install deferred-promise
11+
```
12+
13+
## Documentation
14+
15+
- [**Class: `DeferredPromise`**](#class-deferredpromise)
16+
- [`new DeferredPromise()`](#new-defferedpromise)
17+
- [`deferredPromise.state`](#deferredpromisestate)
18+
- [`deferredProimse.result`](#deferredpromiseresult)
19+
- [`deferredPromise.resolve()`](#deferredpromiseresolve)
20+
- [`deferredPromies.reject()`](#deferredpromisereject)
21+
22+
## Class: `DeferredPromise`
23+
24+
### `new DefferedPromise()`
25+
26+
Creates a new instance of a deferred promise.
27+
28+
```js
29+
import { DeferredPromise } from 'deferred-promise'
30+
31+
const promise = new DeferredPromise()
32+
```
33+
34+
Unlike the regular `Promise`, a deferred promise does not accept the callback function. Instead, you should use [`.resolve()`](#deferredpromiseresolve) and [`.reject()`](#deferredpromisereject) to resolve and reject the promise respectively.
35+
36+
A deferred promise is fully compatible with the native `Promise`, which means you can pass it to the consumers that await a regular `Promise` as well.
37+
38+
### `deferredPromise.state`
39+
40+
- `<"pending" | "resolved" | "rejected">` **Default:** `"pending"`
41+
42+
```js
43+
const promise = new DeferredPromise()
44+
console.log(promise.state) // "pending"
45+
46+
promise.resolve()
47+
console.log(promise.state) // "resolved"
48+
```
49+
50+
### `deferredPromise.result`
51+
52+
Returns the value that has resolved the promise. If no value has been provided to the `.resolve()` call, `undefined` is returned instead.
53+
54+
```js
55+
const promise = new DeferredPromise()
56+
promise.resolve('John')
57+
58+
console.log(promise.result) // "John"
59+
```
60+
61+
### `deferredPromise.rejectionReason`
62+
63+
Returns the reason that has rejected the promise. If no reason has been provided to the `.reject()` call, `undefined` is returned instead.
64+
65+
```js
66+
const promise = new DeferredPromise()
67+
promise.reject(new Error('Internal Server Error'))
68+
69+
console.log(promise.rejectionReason) // Error
70+
```
71+
72+
### `deferredPromise.resolve()`
73+
74+
Resolves the deferred promise with a given value.
75+
76+
```js
77+
function startServer() {
78+
const serverReady = new DeferredPromise()
79+
80+
new http.Server().listen(() => {
81+
// Resolve the deferred promise with the server address
82+
// once the server is ready.
83+
serverReady.resolve('http://localhost:8080')
84+
})
85+
86+
// Return the deferred promise to the consumer.
87+
return serverReady
88+
}
89+
90+
startServer().then((address) => {
91+
console.log('Server is running at "%s"', address)
92+
})
93+
```
94+
95+
### `deferredPromise.reject()`
96+
97+
Rejects the deferred promise with a given reason.
98+
99+
```js
100+
function createBroadcast() {
101+
const runtimePromise = new DeferredPromise()
102+
103+
receiver.on('error', (error) => {
104+
// Reject the deferred promise in response
105+
// to the incoming "error" event.
106+
runtimePromise.reject(error)
107+
})
108+
109+
// This deferred promise will be pending forever
110+
// unless the broadcast channel receives the
111+
// "error" event that rejects it.
112+
return runtimePromise
113+
}
114+
```

src/DeferredPromise.ts

+14-13
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,18 @@ export type RejectFunction<Result = void> = (reason?: unknown) => Result
1616
* const portReady = new DeferredPromise()
1717
* portReady.reject(new Error('Port is already in use'))
1818
*/
19-
export class DeferredPromise<Data extends any> extends Promise<Data> {
19+
export class DeferredPromise<Data extends any> {
2020
public resolve: ResolveFunction<Data>
2121
public reject: RejectFunction
2222
public state: DeferredPromiseState
2323
public result?: Data
2424
public rejectionReason?: unknown
2525

26-
constructor() {
27-
let _resolve: ResolveFunction<Data> = () => {}
28-
let _reject: RejectFunction = () => {}
26+
private promise: Promise<Data>
2927

30-
super((resolve, reject) => {
31-
_resolve = (data) => {
28+
constructor() {
29+
this.promise = new Promise((resolve, reject) => {
30+
this.resolve = (data) => {
3231
if (this.state !== 'pending') {
3332
throw new TypeError(
3433
`Cannot resolve a DeferredPromise: illegal state ("${this.state}")`
@@ -40,7 +39,7 @@ export class DeferredPromise<Data extends any> extends Promise<Data> {
4039
resolve(data)
4140
}
4241

43-
_reject = (reason) => {
42+
this.reject = (reason) => {
4443
if (this.state !== 'pending') {
4544
throw new TypeError(
4645
`Cannot reject a DeferredPromise: illegal state ("${this.state}")`
@@ -53,19 +52,21 @@ export class DeferredPromise<Data extends any> extends Promise<Data> {
5352
}
5453
})
5554

56-
this.resolve = _resolve
57-
this.reject = _reject
58-
5955
this.state = 'pending'
6056
this.result = undefined
6157
this.rejectionReason = undefined
6258
}
6359

60+
public then(onresolved?: ResolveFunction<Data>, onrejected?: RejectFunction) {
61+
this.promise.then(onresolved, onrejected)
62+
return this
63+
}
64+
6465
public catch<RejectReason = never>(
6566
onrejected?: RejectFunction<RejectReason>
66-
): DeferredPromise<Data | RejectReason> {
67-
super.catch(onrejected)
68-
return this as any
67+
): this {
68+
this.promise.catch<RejectReason>(onrejected)
69+
return this
6970
}
7071

7172
static get [Symbol.species]() {

test/DeferredPromise.test.ts

+7-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ it('can be listened to with ".then()"', (done) => {
44
expect.assertions(1)
55

66
const promise = new DeferredPromise<number>()
7+
78
promise.then((data) => {
89
expect(data).toBe(123)
910
done()
@@ -35,6 +36,7 @@ it('can be awaited', async () => {
3536
describe('resolve()', () => {
3637
it('can be resolved without data', () => {
3738
const promise = new DeferredPromise<void>()
39+
expect(promise.state).toBe('pending')
3840
promise.resolve()
3941

4042
expect(promise.state).toBe('resolved')
@@ -53,6 +55,7 @@ describe('resolve()', () => {
5355

5456
it('throws when resolving an already resolved promise', () => {
5557
const promise = new DeferredPromise<number>()
58+
expect(promise.state).toBe('pending')
5659
promise.resolve(123)
5760

5861
expect(() => promise.resolve(456)).toThrow(
@@ -64,6 +67,7 @@ describe('resolve()', () => {
6467

6568
it('throws when resolving an already rejected promise', () => {
6669
const promise = new DeferredPromise<number>().catch(() => {})
70+
expect(promise.state).toBe('pending')
6771
promise.reject()
6872

6973
expect(() => promise.resolve(123)).toThrow(
@@ -76,7 +80,8 @@ describe('resolve()', () => {
7680

7781
describe('reject()', () => {
7882
it('can be rejected without any reason', () => {
79-
const promise = new DeferredPromise<void>()
83+
const promise = new DeferredPromise<void>().catch(() => {})
84+
expect(promise.state).toBe('pending')
8085
promise.reject()
8186

8287
expect(promise.state).toBe('rejected')
@@ -85,7 +90,7 @@ describe('reject()', () => {
8590
})
8691

8792
it('can be rejected with a reason', () => {
88-
const promise = new DeferredPromise()
93+
const promise = new DeferredPromise().catch(() => {})
8994
expect(promise.state).toBe('pending')
9095

9196
const rejectionReason = new Error('Something went wrong')

0 commit comments

Comments
 (0)