@flex-development/when 1.0.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +49 -0
- package/README.md +820 -108
- package/dist/index.d.mts +526 -51
- package/dist/lib/index.mjs +5 -1
- package/dist/lib/is-catchable.mjs +24 -0
- package/dist/lib/is-finalizable.mjs +24 -0
- package/dist/lib/is-promise-like.mjs +24 -0
- package/dist/lib/is-promise.mjs +37 -0
- package/dist/lib/is-thenable.mjs +33 -9
- package/dist/lib/when.mjs +102 -19
- package/dist/testing/index.d.mts +149 -0
- package/dist/testing/index.mjs +5 -0
- package/dist/testing/lib/create-thenable.mjs +350 -0
- package/dist/testing/lib/index.mjs +5 -0
- package/package.json +25 -9
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file createThenable
|
|
3
|
+
* @module when/testing/lib/createThenable
|
|
4
|
+
*/
|
|
5
|
+
import isThenable from '#lib/is-thenable';
|
|
6
|
+
export default createThenable;
|
|
7
|
+
/**
|
|
8
|
+
* Create a thenable.
|
|
9
|
+
*
|
|
10
|
+
* The returned object conforms to {@linkcode Thenable} and ensures `then`
|
|
11
|
+
* always returns another {@linkcode Thenable}, even when adopting a foreign
|
|
12
|
+
* thenable.
|
|
13
|
+
*
|
|
14
|
+
* When `options` is omitted, `null`, or `undefined`, the returned thenable is
|
|
15
|
+
* *modern* (a thenable with `then`, `catch`, and `finally` methods).
|
|
16
|
+
* Pass an options object (e.g. `{}`) to start from a *bare* (`then` method
|
|
17
|
+
* only) thenable and selectively enable methods.
|
|
18
|
+
*
|
|
19
|
+
* @see {@linkcode CreateThenableOptions}
|
|
20
|
+
* @see {@linkcode Executor}
|
|
21
|
+
* @see {@linkcode Thenable}
|
|
22
|
+
*
|
|
23
|
+
* @template {any} T
|
|
24
|
+
* The resolved value
|
|
25
|
+
* @template {any} [Reason=Error]
|
|
26
|
+
* The reason for a rejection
|
|
27
|
+
* @template {Thenable<T>} [Result=Thenable<T>]
|
|
28
|
+
* The thenable
|
|
29
|
+
*
|
|
30
|
+
* @this {void}
|
|
31
|
+
*
|
|
32
|
+
* @param {Executor<T, Reason>} executor
|
|
33
|
+
* The initialization callback
|
|
34
|
+
* @param {CreateThenableOptions | null | undefined} [options]
|
|
35
|
+
* Options for creating a thenable
|
|
36
|
+
* @return {Result}
|
|
37
|
+
* The thenable
|
|
38
|
+
*/
|
|
39
|
+
function createThenable(executor, options) {
|
|
40
|
+
/**
|
|
41
|
+
* Whether the thenable has been settled.
|
|
42
|
+
*
|
|
43
|
+
* @var {boolean} settled
|
|
44
|
+
*/
|
|
45
|
+
let settled = false;
|
|
46
|
+
/**
|
|
47
|
+
* The settled state.
|
|
48
|
+
*
|
|
49
|
+
* @var {State<T, Reason> | undefined} state
|
|
50
|
+
*/
|
|
51
|
+
let state;
|
|
52
|
+
try {
|
|
53
|
+
executor(ok, nok);
|
|
54
|
+
}
|
|
55
|
+
catch (e) {
|
|
56
|
+
nok(e);
|
|
57
|
+
}
|
|
58
|
+
// executor neither resolved nor rejected, treat as `ok(undefined)`.
|
|
59
|
+
if (!state)
|
|
60
|
+
ok(undefined);
|
|
61
|
+
return wrapState(state);
|
|
62
|
+
/**
|
|
63
|
+
* Adopt an awaitable into a {@linkcode Thenable}.
|
|
64
|
+
*
|
|
65
|
+
* If `x` is a thenable, the returned thenable follows its settlement.
|
|
66
|
+
* Otherwise, the returned thenable is immediately resolved with `x`.
|
|
67
|
+
*
|
|
68
|
+
* @template {any} X
|
|
69
|
+
* The resolved value
|
|
70
|
+
*
|
|
71
|
+
* @this {void}
|
|
72
|
+
*
|
|
73
|
+
* @param {Awaitable<X>} x
|
|
74
|
+
* The awaitable to adopt
|
|
75
|
+
* @return {Thenable<X>}
|
|
76
|
+
* A thenable representing the adopted awaitable
|
|
77
|
+
*/
|
|
78
|
+
function adopt(x) {
|
|
79
|
+
if (isThenable(x)) {
|
|
80
|
+
// wrap foreign thenable so `then` still returns a thenable.
|
|
81
|
+
return addMethods({
|
|
82
|
+
/**
|
|
83
|
+
* @template {any} [Next=X]
|
|
84
|
+
* The next resolved value on success
|
|
85
|
+
* @template {any} [Failure=never]
|
|
86
|
+
* The next resolved value on failure
|
|
87
|
+
*
|
|
88
|
+
* @this {void}
|
|
89
|
+
*
|
|
90
|
+
* @param {OnFulfilled<X, Next> | null | undefined} [onfulfilled]
|
|
91
|
+
* The callback to execute when the thenable is resolved
|
|
92
|
+
* @param {OnRejected<Failure, Reason> | null | undefined} [onrejected]
|
|
93
|
+
* The callback to execute when the thenable is rejected
|
|
94
|
+
* @return {Thenable<Failure | Next>}
|
|
95
|
+
* The next thenable
|
|
96
|
+
*/
|
|
97
|
+
then(onfulfilled, onrejected) {
|
|
98
|
+
return adopt(x.then(
|
|
99
|
+
/**
|
|
100
|
+
* @this {void}
|
|
101
|
+
*
|
|
102
|
+
* @param {X} value
|
|
103
|
+
* The resolved value
|
|
104
|
+
* @return {Awaitable<Next>}
|
|
105
|
+
* The next awaitable
|
|
106
|
+
*/
|
|
107
|
+
function succ(value) {
|
|
108
|
+
return resolve(value, onfulfilled);
|
|
109
|
+
},
|
|
110
|
+
/**
|
|
111
|
+
* @this {void}
|
|
112
|
+
*
|
|
113
|
+
* @param {unknown} e
|
|
114
|
+
* The reason for the rejection
|
|
115
|
+
* @return {Awaitable<Failure>}
|
|
116
|
+
* The next awaitable
|
|
117
|
+
*/
|
|
118
|
+
function fail(e) {
|
|
119
|
+
return reject(e, onrejected);
|
|
120
|
+
}));
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
return wrapState({ ok: true, value: x });
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Add requested methods to a `thenable`.
|
|
128
|
+
*
|
|
129
|
+
* @template {any} U
|
|
130
|
+
* The resolved value
|
|
131
|
+
*
|
|
132
|
+
* @this {void}
|
|
133
|
+
*
|
|
134
|
+
* @param {Thenable<U>} thenable
|
|
135
|
+
* The current thenable
|
|
136
|
+
* @return {Thenable<U>}
|
|
137
|
+
* The `thenable`
|
|
138
|
+
*/
|
|
139
|
+
function addMethods(thenable) {
|
|
140
|
+
if (!options) {
|
|
141
|
+
thenable.catch = katch;
|
|
142
|
+
thenable.finally = finalize;
|
|
143
|
+
return thenable;
|
|
144
|
+
}
|
|
145
|
+
if (options.catch) {
|
|
146
|
+
thenable.catch = katch;
|
|
147
|
+
}
|
|
148
|
+
else if ('catch' in options && options.catch !== false) {
|
|
149
|
+
thenable.catch = options.catch;
|
|
150
|
+
}
|
|
151
|
+
if (options.finally) {
|
|
152
|
+
thenable.finally = finalize;
|
|
153
|
+
}
|
|
154
|
+
else if ('finally' in options && options.finally !== false) {
|
|
155
|
+
thenable.finally = options.finally;
|
|
156
|
+
}
|
|
157
|
+
return thenable;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Attach a callback that is invoked only when `this` thenable is settled.
|
|
161
|
+
*
|
|
162
|
+
* @template {any} U
|
|
163
|
+
* The resolved value
|
|
164
|
+
*
|
|
165
|
+
* @this {Thenable<U>}
|
|
166
|
+
*
|
|
167
|
+
* @param {OnFinally | null | undefined} [onfinally]
|
|
168
|
+
* The callback to execute when the thenable is settled
|
|
169
|
+
* @return {Thenable<U>}
|
|
170
|
+
* The next thenable
|
|
171
|
+
*/
|
|
172
|
+
function finalize(onfinally) {
|
|
173
|
+
return typeof onfinally === 'function' ? this.then(succ, fail) : this;
|
|
174
|
+
/**
|
|
175
|
+
* @this {void}
|
|
176
|
+
*
|
|
177
|
+
* @param {unknown} reason
|
|
178
|
+
* The reason for the rejection
|
|
179
|
+
* @return {never}
|
|
180
|
+
* Never; throws `reason`
|
|
181
|
+
* @throws {unknown}
|
|
182
|
+
*/
|
|
183
|
+
function fail(reason) {
|
|
184
|
+
void onfinally();
|
|
185
|
+
throw reason;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* @this {void}
|
|
189
|
+
*
|
|
190
|
+
* @param {U} value
|
|
191
|
+
* The resolved value
|
|
192
|
+
* @return {U}
|
|
193
|
+
* The resolved `value`
|
|
194
|
+
*/
|
|
195
|
+
function succ(value) {
|
|
196
|
+
return void onfinally(), value;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Attach a callback only for the rejection of `this` thenable.
|
|
201
|
+
*
|
|
202
|
+
* @template {any} U
|
|
203
|
+
* The resolved value
|
|
204
|
+
* @template {any} [Failure=never]
|
|
205
|
+
* The resolved value on failure
|
|
206
|
+
*
|
|
207
|
+
* @this {Thenable<U>}
|
|
208
|
+
*
|
|
209
|
+
* @param {OnRejected<Failure, Reason> | null | undefined} [onrejected]
|
|
210
|
+
* The callback to execute when the thenable is rejected
|
|
211
|
+
* @return {Thenable<Failure | U>}
|
|
212
|
+
* The next thenable
|
|
213
|
+
*/
|
|
214
|
+
function katch(onrejected) {
|
|
215
|
+
return this.then(null, onrejected);
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Reject the thenable.
|
|
219
|
+
*
|
|
220
|
+
* @this {void}
|
|
221
|
+
*
|
|
222
|
+
* @param {Reason} reason
|
|
223
|
+
* The reason for the rejection
|
|
224
|
+
* @return {undefined}
|
|
225
|
+
*/
|
|
226
|
+
function nok(reason) {
|
|
227
|
+
return void (settled || (settled = true, state = { ok: false, reason }));
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Resolve the thenable.
|
|
231
|
+
*
|
|
232
|
+
* @this {void}
|
|
233
|
+
*
|
|
234
|
+
* @param {Awaitable<T>} value
|
|
235
|
+
* The awaitable
|
|
236
|
+
* @return {undefined}
|
|
237
|
+
*/
|
|
238
|
+
function ok(value) {
|
|
239
|
+
return void (settled || (settled = true, state = { ok: true, value }));
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Handle a rejection.
|
|
243
|
+
*
|
|
244
|
+
* @template {any} T
|
|
245
|
+
* The next resolved value
|
|
246
|
+
*
|
|
247
|
+
* @this {void}
|
|
248
|
+
*
|
|
249
|
+
* @param {unknown} reason
|
|
250
|
+
* The reason for the rejection
|
|
251
|
+
* @param {OnRejected<T, Reason> | null | undefined} [onrejected]
|
|
252
|
+
* The callback to execute when the thenable is rejected
|
|
253
|
+
* @return {Awaitable<T>}
|
|
254
|
+
* The next awaitable
|
|
255
|
+
*/
|
|
256
|
+
function reject(reason, onrejected) {
|
|
257
|
+
if (typeof onrejected === 'function')
|
|
258
|
+
return onrejected(reason);
|
|
259
|
+
return wrapState({ ok: false, reason: reason });
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Handle a resolved `value`.
|
|
263
|
+
*
|
|
264
|
+
* @template {any} T
|
|
265
|
+
* The resolved value
|
|
266
|
+
* @template {any} Next
|
|
267
|
+
* The next resolved value
|
|
268
|
+
* @this {void}
|
|
269
|
+
*
|
|
270
|
+
* @param {T} value
|
|
271
|
+
* The resolved value
|
|
272
|
+
* @return {Awaitable<Next>}
|
|
273
|
+
* The next awaitable
|
|
274
|
+
*/
|
|
275
|
+
function resolve(value, onfulfilled) {
|
|
276
|
+
if (typeof onfulfilled === 'function')
|
|
277
|
+
return onfulfilled(value);
|
|
278
|
+
return value;
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Wrap a settled state into a {@linkcode Thenable}.
|
|
282
|
+
*
|
|
283
|
+
* @template {any} S
|
|
284
|
+
* The resolved value
|
|
285
|
+
*
|
|
286
|
+
* @this {void}
|
|
287
|
+
*
|
|
288
|
+
* @param {State} s
|
|
289
|
+
* The settled state
|
|
290
|
+
* @return {Thenable<S>}
|
|
291
|
+
* A thenable representing the settled state
|
|
292
|
+
*/
|
|
293
|
+
function wrapState(s) {
|
|
294
|
+
return addMethods({
|
|
295
|
+
/**
|
|
296
|
+
* @template {any} [Next=S]
|
|
297
|
+
* The next resolved value on success
|
|
298
|
+
* @template {any} [Failure=never]
|
|
299
|
+
* The next resolved value on failure
|
|
300
|
+
*
|
|
301
|
+
* @this {void}
|
|
302
|
+
*
|
|
303
|
+
* @param {OnFulfilled<S, Next> | null | undefined} [onfulfilled]
|
|
304
|
+
* The callback to execute when the thenable is resolved
|
|
305
|
+
* @param {OnRejected<Failure, Reason> | null | undefined} [onrejected]
|
|
306
|
+
* The callback to execute when the thenable is rejected
|
|
307
|
+
* @return {Thenable<Failure | Next>}
|
|
308
|
+
* The next thenable
|
|
309
|
+
*/
|
|
310
|
+
then(onfulfilled, onrejected) {
|
|
311
|
+
try {
|
|
312
|
+
if (s.ok) {
|
|
313
|
+
if (isThenable(s.value))
|
|
314
|
+
return adopt(s.value).then(succ, fail);
|
|
315
|
+
return adopt(succ(s.value));
|
|
316
|
+
/**
|
|
317
|
+
* @this {void}
|
|
318
|
+
*
|
|
319
|
+
* @param {S} value
|
|
320
|
+
* The resolved value
|
|
321
|
+
* @return {Awaitable<Next>}
|
|
322
|
+
* The next awaitable
|
|
323
|
+
*/
|
|
324
|
+
function succ(value) {
|
|
325
|
+
return resolve(value, onfulfilled);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
// rejected.
|
|
329
|
+
if (typeof onrejected === 'function')
|
|
330
|
+
return adopt(fail(s.reason));
|
|
331
|
+
return wrapState({ ok: false, reason: s.reason });
|
|
332
|
+
}
|
|
333
|
+
catch (e) {
|
|
334
|
+
return wrapState({ ok: false, reason: e });
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* @this {void}
|
|
338
|
+
*
|
|
339
|
+
* @param {unknown} e
|
|
340
|
+
* The reason for the rejection
|
|
341
|
+
* @return {Awaitable<Failure>}
|
|
342
|
+
* The next awaitable
|
|
343
|
+
*/
|
|
344
|
+
function fail(e) {
|
|
345
|
+
return reject(e, onrejected);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
}
|
package/package.json
CHANGED
|
@@ -1,19 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flex-development/when",
|
|
3
|
-
"description": "
|
|
4
|
-
"version": "
|
|
3
|
+
"description": "Chain callbacks on synchronous values or thenables without forcing Promise.resolve or microtasks.",
|
|
4
|
+
"version": "3.0.0",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"async",
|
|
7
7
|
"await",
|
|
8
8
|
"awaitable",
|
|
9
9
|
"callback",
|
|
10
10
|
"chain",
|
|
11
|
+
"esm",
|
|
12
|
+
"functional",
|
|
13
|
+
"hooks",
|
|
14
|
+
"microtask",
|
|
15
|
+
"pipeline",
|
|
11
16
|
"promise",
|
|
17
|
+
"promise-like",
|
|
12
18
|
"sync",
|
|
13
19
|
"synchronous",
|
|
14
20
|
"then",
|
|
15
21
|
"thenable",
|
|
16
|
-
"typescript"
|
|
22
|
+
"typescript",
|
|
23
|
+
"utility"
|
|
17
24
|
],
|
|
18
25
|
"license": "BSD-3-Clause",
|
|
19
26
|
"homepage": "https://github.com/flex-development/when",
|
|
@@ -48,7 +55,11 @@
|
|
|
48
55
|
"when": "./src/index.mts",
|
|
49
56
|
"default": "./dist/index.mjs"
|
|
50
57
|
},
|
|
51
|
-
"./package.json": "./package.json"
|
|
58
|
+
"./package.json": "./package.json",
|
|
59
|
+
"./testing": {
|
|
60
|
+
"when": "./src/testing/index.mts",
|
|
61
|
+
"default": "./dist/testing/index.mjs"
|
|
62
|
+
}
|
|
52
63
|
},
|
|
53
64
|
"imports": {
|
|
54
65
|
"#fixtures/*": "./__fixtures__/*.mts",
|
|
@@ -60,6 +71,10 @@
|
|
|
60
71
|
"when": "./src/lib/*.mts",
|
|
61
72
|
"default": "./dist/lib/*.mjs"
|
|
62
73
|
},
|
|
74
|
+
"#testing/*": {
|
|
75
|
+
"when": "./src/testing/*.mts",
|
|
76
|
+
"default": "./dist/testing/*.mjs"
|
|
77
|
+
},
|
|
63
78
|
"#tests/*": "./__tests__/*.mts",
|
|
64
79
|
"#types/*": {
|
|
65
80
|
"when": "./src/types/*.mts",
|
|
@@ -75,7 +90,7 @@
|
|
|
75
90
|
},
|
|
76
91
|
"scripts": {
|
|
77
92
|
"build": "yarn clean:build && tsc -p tsconfig.build.json --noEmit false && yarn bundle:dts",
|
|
78
|
-
"bundle:dts": "rollup --config=rollup.config.mts && trash dist
|
|
93
|
+
"bundle:dts": "rollup --config=rollup.config.mts && trash dist/**/{interfaces,types} && trash dist/**/lib/*.d.mts",
|
|
79
94
|
"check:ci": "yarn dedupe --check && yarn check:format && yarn check:lint && yarn check:spelling && yarn typecheck && yarn check:types && yarn test:cov && yarn pack && yarn check:types:build && attw package.tgz && yarn clean:pack",
|
|
80
95
|
"check:format": "dprint check --incremental=false",
|
|
81
96
|
"check:lint": "eslint --exit-on-fatal-error --max-warnings 0 .",
|
|
@@ -114,9 +129,10 @@
|
|
|
114
129
|
},
|
|
115
130
|
"devDependencies": {
|
|
116
131
|
"@arethetypeswrong/cli": "0.18.2",
|
|
117
|
-
"@commitlint/cli": "20.4.
|
|
132
|
+
"@commitlint/cli": "20.4.2",
|
|
118
133
|
"@commitlint/types": "20.4.0",
|
|
119
134
|
"@edge-runtime/vm": "5.0.0",
|
|
135
|
+
"@faker-js/faker": "10.3.0",
|
|
120
136
|
"@flex-development/commitlint-config": "1.0.1",
|
|
121
137
|
"@flex-development/eslint-config": "1.1.1",
|
|
122
138
|
"@flex-development/grease": "3.0.0-alpha.9",
|
|
@@ -134,20 +150,20 @@
|
|
|
134
150
|
"@vitest/ui": "4.0.18",
|
|
135
151
|
"concat-stream": "2.0.0",
|
|
136
152
|
"cross-env": "10.1.0",
|
|
137
|
-
"cspell": "9.
|
|
153
|
+
"cspell": "9.7.0",
|
|
138
154
|
"devlop": "1.1.0",
|
|
139
155
|
"dprint": "0.51.1",
|
|
140
156
|
"editorconfig": "3.0.1",
|
|
141
157
|
"eslint": "9.39.2",
|
|
142
158
|
"growl": "1.10.5",
|
|
143
|
-
"happy-dom": "20.
|
|
159
|
+
"happy-dom": "20.7.0",
|
|
144
160
|
"husky": "9.1.7",
|
|
145
161
|
"is-ci": "4.1.0",
|
|
146
162
|
"node-notifier": "10.0.1",
|
|
147
163
|
"pkg-size": "2.4.0",
|
|
148
164
|
"remark": "15.0.1",
|
|
149
165
|
"remark-cli": "12.0.1",
|
|
150
|
-
"rollup": "4.
|
|
166
|
+
"rollup": "4.59.0",
|
|
151
167
|
"rollup-plugin-dts": "6.3.0",
|
|
152
168
|
"sh-syntax": "0.5.8",
|
|
153
169
|
"trash-cli": "7.2.0",
|