You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat: add deferred executor, support chaining (#4)
BREAKING CHANGE: Deferred promises are now implemented via the deferred
executor function. There are no changes to the existing public API.
## Changes
- Supports Promise chaining.
- Adds `createDeferredExecutor` to use this library with any `Promise`.
- Improves type annotations in regard to what the deferred promise
expects as the input and what is the promise's output:
```ts
const p = new DeferredPromise<number>()
.then(() => 'hello')
// The input must be "number".
p.resolve(5)
// the output is "string"
// (the result of the "then" chain)
await p // "hello"
```
- **Deprecated:** Removes `DeferredPromise.result`. Access the result by
awaiting the promise instead.
- **Deprecated:** The `resolved` promise state is replaced by
`fulfilled`, which is the correct promise state representation per
specification.
## Roadmap
- [x] Document the deferred executor with more detail and then mention
it in the deferred promise class documentation. That's exactly their
relationship.
- [x] Support promises returned from `.then`/`.catch`.
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 rejectthe promise respectively.
43
+
Deferred executor allows you to control any promise remotely and doesn't affect the Promise instance in any way. Similar to the [`DeferredPromise`](#class-deferredpromise) instance, the deferred executor exposes additional promise properties like `state`, `rejectionReason`, `resolve`, and `reject`. In fact, the `DeferredPromise` class is implemented on top of the deferred executor.
36
44
37
-
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.
Calling [`resolve()`](#deferredexecutorresolve) and [`reject()`](#deferredexecutorreject) methods of the executor transitions the state to "fulfilled" and "rejected" respectively.
52
68
53
-
Returns the value that has resolved the promise. If no value has been provided to the `.resolve()` call, `undefined` is returned instead.
69
+
### `DeferredExecutor.resolve()`
70
+
71
+
Resolves the promise with a given value.
54
72
55
73
```js
56
-
constpromise=newDeferredPromise();
57
-
promise.resolve("John");
74
+
constexecutor=createDeferredExecutor()
75
+
constpromise=newPromise(executor)
76
+
77
+
console.log(executor.state) // "pending"
78
+
79
+
executor.resolve()
58
80
59
-
console.log(promise.result); // "John"
81
+
// The promise state is still "pending"
82
+
// because promises are settled in the next microtask.
83
+
console.log(executor.state) // "pending"
84
+
85
+
nextTick(() => {
86
+
// In the next microtask, the promise's state is resolved.
87
+
console.log(executor.state) // "fulfilled"
88
+
})
60
89
```
61
90
62
-
### `deferredPromise.rejectionReason`
91
+
### `DeferredExecutor.reject()`
63
92
64
-
Returns the reason that has rejected the promise. If no reason has been provided to the `.reject()` call, `undefined` is returned instead.
93
+
Rejects the promise with a given reason.
65
94
66
95
```js
67
-
constpromise=newDeferredPromise();
68
-
promise.reject(newError("Internal Server Error"));
96
+
constexecutor=createDeferredExecutor()
97
+
constpromise=newPromise(executor)
98
+
99
+
executor.reject(newError('Failed to fetch'))
69
100
70
-
console.log(promise.rejectionReason); // Error
101
+
nextTick(() => {
102
+
console.log(executor.state) // "rejected"
103
+
console.log(executor.rejectionReason) // Error("Failed to fetch")
104
+
})
71
105
```
72
106
73
-
### `deferredPromise.resolve()`
107
+
You can access the rejection reason of the promise at any time by the [`rejectionReason`](#deferredexecutorrejectionreason) property of the deferred executor.
108
+
109
+
### `DeferredExecutor.rejectionReason`
74
110
75
-
Resolves the deferred promise with a given value.
111
+
Returns the reason of the promise rejection. If no reason has been provided to the `reject()` call, `undefined` is returned instead.
76
112
77
113
```js
78
-
functionstartServer() {
79
-
constserverReady=newDeferredPromise();
114
+
constexecutor=createDeferredExecutor()
115
+
constpromise=newPromise(executor)
80
116
81
-
newhttp.Server().listen(() => {
82
-
// Resolve the deferred promise with the server address
83
-
// once the server is ready.
84
-
serverReady.resolve("http://localhost:8080");
85
-
});
117
+
promise.reject(newError('Internal Server Error'))
86
118
87
-
// Return the deferred promise to the consumer.
88
-
return serverReady;
89
-
}
90
-
91
-
startServer().then((address) => {
92
-
console.log('Server is running at "%s"', address);
93
-
});
119
+
nextTick(() => {
120
+
console.log(promise.rejectionReason) // Error("Internal Server Error")
A deferred promise is a Promise-compatible class that constructs a regular Promise instance under the hood, controlling it via the [deferred executor](#createdeferredexecutor).
118
139
119
-
Attaches a callback that executes when the deferred promise is settled (resolved or rejected).
140
+
A deferred promise is fully compatible with the regular Promise, both type- and runtime-wise, e.g. a deferred promise can be chained and awaited normally.
120
141
121
142
```js
122
-
constchannelReady=newDeferredPromise();
143
+
constpromise=newDefferredPromise()
144
+
.then((value) =>value.toUpperCase())
145
+
.then((value) =>value.substring(0, 2))
146
+
.catch((error) =>console.error(error))
123
147
124
-
channelReady.finally(async () => {
125
-
// Perform a cleanup side-effect once we're done.
126
-
awaitchannel.close();
127
-
});
148
+
await promise
128
149
```
150
+
151
+
Unlike the regular Promise, however, a deferred promise doesn't accept the `executor` function as the constructor argument. Instead, the resolution of the deferred promise is deferred to the current scope (thus the name).
152
+
153
+
```js
154
+
functiongetPort() {
155
+
// Notice that you don't provide any executor function
156
+
// when constructing a deferred promise.
157
+
constportPromise=newDeferredPromise()
158
+
159
+
port.on('open', (port) => {
160
+
// Resolve the deferred promise whenever necessary.
161
+
portPromise.resolve(port)
162
+
})
163
+
164
+
// Return the deferred promise immediately.
165
+
return portPromise
166
+
}
167
+
```
168
+
169
+
Use the [`resolve()`](#deferredpromiseresolve) and [`reject()`](#deferredpromisereject) methods of the deferred promise instance to resolve and reject that promise respectively.
170
+
171
+
### `deferredPromise.state`
172
+
173
+
See [`DeferredExecutor.state`](#deferredexecutorstate)
174
+
175
+
### `deferredPromise.resolve()`
176
+
177
+
See [`DeferredExecutor.resolve()`](#deferredexecutorresolve)
178
+
179
+
### `deferredPromise.reject()`
180
+
181
+
See [`DeferredExecutor.reject()`](#deferredexecutorreject)
182
+
183
+
### `deferredPromise.rejectionReason`
184
+
185
+
See [`DeferredExecutor.rejectionReason`](#deferredexecutorrejectionreason)
0 commit comments