@flex-development/when 1.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 ADDED
@@ -0,0 +1,73 @@
1
+ ## 1.0.0 (2026-02-18)
2
+
3
+ ### :package: Build
4
+
5
+ - [[`89747c3`](https://github.com/flex-development/when/commit/89747c317883b749a9f9ef20702c7a0847db291b)] **ts:** fix dts bundle
6
+
7
+ ### :robot: Continuous Integration
8
+
9
+ - [[`8472f72`](https://github.com/flex-development/when/commit/8472f7299ee70065318b2b925fccb926e62ef46d)] **deps:** Bump actions/cache from 5.0.1 to 5.0.3 ([#2](https://github.com/flex-development/when/issues/2))
10
+ - [[`a490ffe`](https://github.com/flex-development/when/commit/a490ffea18095d99f07edba0608c5f517e1e60db)] **deps:** Bump actions/checkout from 6.0.1 to 6.0.2 ([#4](https://github.com/flex-development/when/issues/4))
11
+ - [[`cebe06a`](https://github.com/flex-development/when/commit/cebe06ae414c0bde9b126574cdf40bf5a6ba9311)] **deps:** Bump actions/setup-node from 6.1.0 to 6.2.0 ([#3](https://github.com/flex-development/when/issues/3))
12
+ - [[`34b0da9`](https://github.com/flex-development/when/commit/34b0da9ab276073eaa2675ca2072af7183fff021)] **deps:** Bump streetsidesoftware/cspell-action from 8.1.2 to 8.2.0 ([#1](https://github.com/flex-development/when/issues/1))
13
+
14
+ ### :pencil: Documentation
15
+
16
+ - [[`b69462f`](https://github.com/flex-development/when/commit/b69462f9f82ac07ae96920af0f22d57f06567e9f)] use
17
+ - [[`0f9a427`](https://github.com/flex-development/when/commit/0f9a4272e75ef01441e2bf4e5eecc450f0092d2b)] what is this?
18
+ - [[`8391dab`](https://github.com/flex-development/when/commit/8391dabf2049d8eebd9319234ca54e90287e4701)] why not `Promise.resolve`?
19
+
20
+ ### :sparkles: Features
21
+
22
+ - [[`0bdbc47`](https://github.com/flex-development/when/commit/0bdbc4740d57624a8f5ec7a33289c7ba5e96a5c5)] `when`
23
+ - [[`aa84d47`](https://github.com/flex-development/when/commit/aa84d47f2d16b53826c936443799af42a74959db)] **lib:** `isPromiseLike`
24
+ - [[`4fab731`](https://github.com/flex-development/when/commit/4fab7311e0fd2e0a909d0d1ce74a1ab478968ec8)] **lib:** `isThenable`
25
+ - [[`507ad2b`](https://github.com/flex-development/when/commit/507ad2b3cdc8577b4e8c8b0cf5e2adbc7641a4a6)] **types:** `Awaitable`
26
+
27
+ ### :bug: Fixes
28
+
29
+ - [[`5ef99bb`](https://github.com/flex-development/when/commit/5ef99bb77f0b35c3670d72ddba4f77a4bb506235)] **pkg:** default export
30
+ - [[`21ec642`](https://github.com/flex-development/when/commit/21ec642d83071e1d965a23dec609c4d1c662dd84)] **pkg:** library exports
31
+
32
+ ### :house_with_garden: Housekeeping
33
+
34
+ - [[`4cca93d`](https://github.com/flex-development/when/commit/4cca93dda1fbc6dbd863405ecfe88259087c2463)] fix release workflow
35
+ - [[`a5418df`](https://github.com/flex-development/when/commit/a5418dfc0cae7c2ca231e735665d76b625615edc)] initial commit
36
+
37
+ ## 1.0.0 (2026-02-18)
38
+
39
+ ### :package: Build
40
+
41
+ - [[`89747c3`](https://github.com/flex-development/when/commit/89747c317883b749a9f9ef20702c7a0847db291b)] **ts:** fix dts bundle
42
+
43
+ ### :robot: Continuous Integration
44
+
45
+ - [[`8472f72`](https://github.com/flex-development/when/commit/8472f7299ee70065318b2b925fccb926e62ef46d)] **deps:** Bump actions/cache from 5.0.1 to 5.0.3 ([#2](https://github.com/flex-development/when/issues/2))
46
+ - [[`a490ffe`](https://github.com/flex-development/when/commit/a490ffea18095d99f07edba0608c5f517e1e60db)] **deps:** Bump actions/checkout from 6.0.1 to 6.0.2 ([#4](https://github.com/flex-development/when/issues/4))
47
+ - [[`cebe06a`](https://github.com/flex-development/when/commit/cebe06ae414c0bde9b126574cdf40bf5a6ba9311)] **deps:** Bump actions/setup-node from 6.1.0 to 6.2.0 ([#3](https://github.com/flex-development/when/issues/3))
48
+ - [[`34b0da9`](https://github.com/flex-development/when/commit/34b0da9ab276073eaa2675ca2072af7183fff021)] **deps:** Bump streetsidesoftware/cspell-action from 8.1.2 to 8.2.0 ([#1](https://github.com/flex-development/when/issues/1))
49
+
50
+ ### :pencil: Documentation
51
+
52
+ - [[`b69462f`](https://github.com/flex-development/when/commit/b69462f9f82ac07ae96920af0f22d57f06567e9f)] use
53
+ - [[`0f9a427`](https://github.com/flex-development/when/commit/0f9a4272e75ef01441e2bf4e5eecc450f0092d2b)] what is this?
54
+ - [[`8391dab`](https://github.com/flex-development/when/commit/8391dabf2049d8eebd9319234ca54e90287e4701)] why not `Promise.resolve`?
55
+
56
+ ### :sparkles: Features
57
+
58
+ - [[`0bdbc47`](https://github.com/flex-development/when/commit/0bdbc4740d57624a8f5ec7a33289c7ba5e96a5c5)] `when`
59
+ - [[`aa84d47`](https://github.com/flex-development/when/commit/aa84d47f2d16b53826c936443799af42a74959db)] **lib:** `isPromiseLike`
60
+ - [[`4fab731`](https://github.com/flex-development/when/commit/4fab7311e0fd2e0a909d0d1ce74a1ab478968ec8)] **lib:** `isThenable`
61
+ - [[`507ad2b`](https://github.com/flex-development/when/commit/507ad2b3cdc8577b4e8c8b0cf5e2adbc7641a4a6)] **types:** `Awaitable`
62
+
63
+ ### :bug: Fixes
64
+
65
+ - [[`5ef99bb`](https://github.com/flex-development/when/commit/5ef99bb77f0b35c3670d72ddba4f77a4bb506235)] **pkg:** default export
66
+ - [[`21ec642`](https://github.com/flex-development/when/commit/21ec642d83071e1d965a23dec609c4d1c662dd84)] **pkg:** library exports
67
+
68
+ ### :house_with_garden: Housekeeping
69
+
70
+ - [[`a5418df`](https://github.com/flex-development/when/commit/a5418dfc0cae7c2ca231e735665d76b625615edc)] initial commit
71
+
72
+
73
+
package/LICENSE.md ADDED
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2022, Flex Development, LLC All rights reserved.
4
+
5
+ Redistribution and use in source and binary forms, with or without modification,
6
+ are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its contributors
16
+ may be used to endorse or promote products derived from this software without
17
+ specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
23
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
26
+ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package/README.md ADDED
@@ -0,0 +1,558 @@
1
+ # \:timer\_clock: when
2
+
3
+ [![github release](https://img.shields.io/github/v/release/flex-development/when.svg?include_prereleases\&sort=semver)](https://github.com/flex-development/when/releases/latest)
4
+ [![npm](https://img.shields.io/npm/v/@flex-development/when.svg)](https://npmjs.com/package/@flex-development/when)
5
+ [![npm downloads](https://img.shields.io/npm/dm/@flex-development/when.svg)](https://www.npmcharts.com/compare/@flex-development/when?interval=30)
6
+ [![install size](https://packagephobia.now.sh/badge?p=@flex-development/when)](https://packagephobia.now.sh/result?p=@flex-development/when)
7
+ [![codecov](https://codecov.io/github/flex-development/when/graph/badge.svg?token=BG38l3Clm1)](https://codecov.io/github/flex-development/when)
8
+ [![module type: esm](https://img.shields.io/badge/module%20type-esm-brightgreen)](https://github.com/voxpelli/badges-cjs-esm)
9
+ [![license](https://img.shields.io/github/license/flex-development/when.svg)](LICENSE.md)
10
+ [![conventional commits](https://img.shields.io/badge/-conventional%20commits-fe5196?logo=conventional-commits\&logoColor=ffffff)](https://conventionalcommits.org)
11
+ [![typescript](https://img.shields.io/badge/-typescript-3178c6?logo=typescript\&logoColor=ffffff)](https://typescriptlang.org)
12
+ [![vitest](https://img.shields.io/badge/-vitest-6e9f18?style=flat\&logo=vitest\&logoColor=ffffff)](https://vitest.dev)
13
+ [![yarn](https://img.shields.io/badge/-yarn-2c8ebb?style=flat\&logo=yarn\&logoColor=ffffff)](https://yarnpkg.com)
14
+
15
+ like `.then`, but for synchronous values *and* thenables.
16
+
17
+ ## Contents
18
+
19
+ - [What is this?](#what-is-this)
20
+ - [Why not `Promise.resolve`?](#why-not-promiseresolve)
21
+ - [Install](#install)
22
+ - [Use](#use)
23
+ - [Chain a synchronous value](#chain-a-synchronous-value)
24
+ - [Chain a *thenable*](#chain-a-thenable)
25
+ - [Pass arguments to the chain callback](#pass-arguments-to-the-chain-callback)
26
+ - [Handle rejections / thrown errors](#handle-rejections--thrown-errors)
27
+ - [Bind `this` context](#bind-this-context)
28
+ - [Use an options object](#use-an-options-object)
29
+ - [API](#api)
30
+ - [`isThenable<T>(value)`][isthenable]
31
+ - [`when<[T][, Next][, Args][, Self]>(value, chain[, reject][, context][, ...args])`][when]
32
+ - [Types](#types)
33
+ - [`Awaitable<T>`][awaitable]
34
+ - [`Chain<[T][, Next][, Args][, Self]>`][chain]
35
+ - [`Options<[T][, Next][, Args][, Self]>`][options]
36
+ - [`Reject<[Next][, Fail][, Self]>`][reject]
37
+ - [Glossary](#glossary)
38
+ - [Project](#project)
39
+ - [Version](#version)
40
+ - [Contribute](#contribute)
41
+
42
+ ## What is this?
43
+
44
+ `when` is a small, but useful package for chaining a callback
45
+ onto an [awaitable][] (a value or a [*thenable*][thenable]).
46
+
47
+ For thenable values, `.then` is used to invoke the callback after resolution.
48
+ Otherwise, the callback is called immediately.
49
+ This makes it easy to write one code path that supports both synchronous and asynchronous values.
50
+
51
+ `when` is especially useful in libraries supporting awaitable APIs or libraries that accept user-provided hooks,
52
+ loaders, or resolvers that may or may not return promises.
53
+
54
+ ### Why not `Promise.resolve`?
55
+
56
+ `when` preserves synchronous values when possible, thus avoiding microtask scheduling and needless promise-allocation
57
+ for these values, as well as making it a great choice for libraries offering API flexibility.
58
+
59
+ ## Install
60
+
61
+ This package is [ESM only][esm].
62
+
63
+ In Node.js (version 20+) with [yarn][]:
64
+
65
+ ```sh
66
+ yarn add @flex-development/when
67
+ ```
68
+
69
+ <blockquote>
70
+ <small>
71
+ See <a href='https://yarnpkg.com/protocol/git'>Git - Protocols | Yarn</a>
72
+ &nbsp;for details regarding installing from Git.
73
+ </small>
74
+ </blockquote>
75
+
76
+ In Deno with [`esm.sh`][esmsh]:
77
+
78
+ ```ts
79
+ import { when } from 'https://esm.sh/@flex-development/when'
80
+ ```
81
+
82
+ In browsers with [`esm.sh`][esmsh]:
83
+
84
+ ```html
85
+ <script type="module">
86
+ import { when } from 'https://esm.sh/@flex-development/when'
87
+ </script>
88
+ ```
89
+
90
+ ## Use
91
+
92
+ ### Chain a synchronous value
93
+
94
+ ```ts
95
+ import { isThenable, when, type Awaitable } from '@flex-development/when'
96
+ import { ok } from 'devlop'
97
+
98
+ /**
99
+ * The result.
100
+ *
101
+ * @const {Awaitable<number>} result
102
+ */
103
+ const result: Awaitable<number> = when(0, n => n + 1)
104
+
105
+ ok(!isThenable(result), 'expected `result` to not be thenable')
106
+ console.dir(result) // 1
107
+ ```
108
+
109
+ ### Chain a [*thenable*][thenable]
110
+
111
+ ```ts
112
+ import { isThenable, when, type Awaitable } from '@flex-development/when'
113
+ import { ok } from 'devlop'
114
+
115
+ /**
116
+ * The result.
117
+ *
118
+ * @const {Awaitable<number>} result
119
+ */
120
+ const result: Awaitable<number> = when(Promise.resolve(2), n => n + 1)
121
+
122
+ ok(isThenable(result), 'expected `result` to be thenable')
123
+ console.dir(await result) // 3
124
+ ```
125
+
126
+ ### Pass arguments to the chain callback
127
+
128
+ Arguments are passed first, and the resolved value is passed last.
129
+
130
+ When the `value` passed to `when` is not [*thenable*][thenable], the resolved value is the same `value`.
131
+
132
+ ```ts
133
+ import when, { type Awaitable } from '@flex-development/when'
134
+
135
+ /**
136
+ * The result.
137
+ *
138
+ * @const {Awaitable<number>} result
139
+ */
140
+ const result: Awaitable<number> = when(1, Math.min, null, undefined, 2, 3, 4)
141
+
142
+ console.dir(result) // 1
143
+ ```
144
+
145
+ ### Handle rejections / thrown errors
146
+
147
+ ```ts
148
+ import when, { type Awaitable } from '@flex-development/when'
149
+
150
+ /**
151
+ * The thenable value.
152
+ *
153
+ * @const {PromiseLike<never>} value
154
+ */
155
+ const value: PromiseLike<never> = new Promise((resolve, reject) => {
156
+ return void reject(new Error('nope', { cause: { url: import.meta.url } }))
157
+ })
158
+
159
+ /**
160
+ * The result.
161
+ *
162
+ * @const {Awaitable<boolean>} result
163
+ */
164
+ const result: Awaitable<boolean> = when(value, chain, reject)
165
+
166
+ console.dir(await result) // false
167
+
168
+ /**
169
+ * @this {void}
170
+ *
171
+ * @return {true}
172
+ * The success result
173
+ */
174
+ function chain(this: void): true {
175
+ return true
176
+ }
177
+
178
+ /**
179
+ * @this {void}
180
+ *
181
+ * @param {Error} e
182
+ * The error to handle
183
+ * @return {false}
184
+ * The failure result
185
+ */
186
+ function reject(this: void, e: Error): false {
187
+ return console.dir(e), false
188
+ }
189
+ ```
190
+
191
+ ### Bind `this` context
192
+
193
+ ```ts
194
+ import when, { type Awaitable } from '@flex-development/when'
195
+
196
+ /**
197
+ * The `this` context.
198
+ */
199
+ type Context = { prefix: string }
200
+
201
+ /**
202
+ * The result.
203
+ *
204
+ * @const {Awaitable<string>} result
205
+ */
206
+ const result: Awaitable<string> = when(13, id, null, { prefix: 'id:' })
207
+
208
+ console.log(result) // 'id:13'
209
+
210
+ /**
211
+ * @this {Context}
212
+ *
213
+ * @param {number | string} num
214
+ * The id number
215
+ * @return {string}
216
+ * The id string
217
+ */
218
+ function id(this: Context, num: number | string): string {
219
+ return this.prefix + num
220
+ }
221
+ ```
222
+
223
+ ### Use an options object
224
+
225
+ ```ts
226
+ import when, { type Awaitable } from '@flex-development/when'
227
+
228
+ /**
229
+ * The `this` context.
230
+ */
231
+ type Context = { errors: Error[] }
232
+
233
+ /**
234
+ * The thenable value.
235
+ *
236
+ * @const {Promise<number>} value
237
+ */
238
+ const value: Promise<number> = new Promise(resolve => resolve(3))
239
+
240
+ /**
241
+ * The result.
242
+ *
243
+ * @const {Awaitable<number | undefined>} result
244
+ */
245
+ const result: Awaitable<number | undefined> = when(value, {
246
+ args: [39],
247
+ chain: divide,
248
+ context: { errors: [] },
249
+ reject
250
+ })
251
+
252
+ console.dir(await result) // 13
253
+
254
+ /**
255
+ * @this {void}
256
+ *
257
+ * @param {number} dividend
258
+ * The number to divide
259
+ * @param {number} divisor
260
+ * The number to divide by
261
+ * @return {number}
262
+ * The quotient
263
+ */
264
+ function divide(this: void, dividend: number, divisor: number): number {
265
+ return dividend / divisor
266
+ }
267
+
268
+ /**
269
+ * @this {Context}
270
+ *
271
+ * @param {Error} e
272
+ * The error to handle
273
+ * @return {undefined}
274
+ */
275
+ function reject(this: Context, e: Error): undefined {
276
+ return void this.errors.push(e)
277
+ }
278
+ ```
279
+
280
+ ## API
281
+
282
+ `when` exports the identifiers listed below.
283
+
284
+ The default export is [`when`][when].
285
+
286
+ ### `isThenable<T>(value)`
287
+
288
+ Check if `value` looks like a [*thenable*][thenable].
289
+
290
+ > 👉 **Note**: Also exported as `isPromiseLike`.
291
+
292
+ #### Type Parameters
293
+
294
+ - `T` (`any`)
295
+ — the resolved value
296
+
297
+ #### Parameters
298
+
299
+ - `value` (`unknown`)
300
+ — the thing to check
301
+
302
+ #### Returns
303
+
304
+ (`value is PromiseLike<T>`) `true` if `value` is a thenable, `false` otherwise
305
+
306
+ <!--lint disable-->
307
+
308
+ ### `when<[T][, Next][, Args][, Self]>(value, chain[, reject][, context][, ...args])`
309
+
310
+ <!--lint enable-->
311
+
312
+ Chain a callback, calling the function after `value` is resolved, or immediately if `value` is not thenable.
313
+
314
+ #### Overloads
315
+
316
+ ```ts
317
+ function when<
318
+ T,
319
+ Next = any,
320
+ Args extends any[] = any[],
321
+ Self = unknown
322
+ >(
323
+ this: void,
324
+ value: Awaitable<T>,
325
+ chain: Chain<T, Next, Args, Self>,
326
+ reject?: Reject<Next, any, Self> | null | undefined,
327
+ context?: Self | null | undefined,
328
+ ...args: Args
329
+ ): Awaitable<Next>
330
+ ```
331
+
332
+ ```ts
333
+ function when<
334
+ T,
335
+ Next = any,
336
+ Args extends any[] = any[],
337
+ Self = unknown
338
+ >(
339
+ this: void,
340
+ value: Awaitable<T>,
341
+ chain: Options<T, Next, Args, Self>
342
+ ): Awaitable<Next>
343
+ ```
344
+
345
+ #### Type Parameters
346
+
347
+ - `T` (`any`)
348
+ — the previously resolved value
349
+ - `Next` (`any`, optional)
350
+ — the next resolved value
351
+ - **default**: `any`
352
+ - `Args` (`readonly any[]`, optional)
353
+ — the function arguments
354
+ - **default**: `any[]`
355
+ - `Self` (`any`, optional)
356
+ — the `this` context
357
+ - **default**: `unknown`
358
+
359
+ #### Parameters
360
+
361
+ - `value` ([`Awaitable<T>`][awaitable])
362
+ — the promise or the resolved value
363
+ - `chain` ([`Chain<T, Next, Args, Self>`][chain] | [`Options<T, Next, Args, Self>`][options])
364
+ — the chain callback or options for chaining
365
+ - `reject` ([`Reject<Next, any, Self>`][reject] | `null` | `undefined`)
366
+ — the callback to fire when a promise is rejected or an error is thrown
367
+ - `context` (`Self` | `null` | `undefined`)
368
+ — the `this` context of the chain and error callbacks
369
+ - `...args` (`Args`)
370
+ — the arguments to pass to the chain callback
371
+
372
+ #### Returns
373
+
374
+ ([`Awaitable<Next>`][awaitable]) The next promise or value
375
+
376
+ ## Types
377
+
378
+ This package is fully typed with [TypeScript][].
379
+
380
+ ### `Awaitable<T>`
381
+
382
+ A synchronous or [*thenable*][thenable] value (`type`).
383
+
384
+ ```ts
385
+ type Awaitable<T> = PromiseLike<T> | T
386
+ ```
387
+
388
+ #### Type Parameters
389
+
390
+ - `T` (`any`)
391
+ — the resolved value
392
+
393
+ ### `Chain<[T][, Next][, Args][, Self]>`
394
+
395
+ A chain callback (`type`).
396
+
397
+ ```ts
398
+ type Chain<
399
+ T = any,
400
+ Next = any,
401
+ Args extends readonly any[] = any[],
402
+ Self = unknown
403
+ > = (this: Self, ...params: [...Args, T]) => Awaitable<Next>
404
+ ```
405
+
406
+ #### Type Parameters
407
+
408
+ - `T` (`any`, optional)
409
+ — the previously resolved value
410
+ - **default**: `any`
411
+ - `Next` (`any`, optional)
412
+ — the next resolved value
413
+ - **default**: `any`
414
+ - `Args` (`readonly any[]`, optional)
415
+ — the function arguments
416
+ - **default**: `any[]`
417
+ - `Self` (`any`, optional)
418
+ — the `this` context
419
+ - **default**: `unknown`
420
+
421
+ #### Parameters
422
+
423
+ - **`this`** (`Self`)
424
+ - `...params` (`[...Args, T]`)
425
+ — the function parameters, with the last being the previously resolved value.
426
+ in cases where a promise is not being resolved, this is the same `value` passed to `when`
427
+
428
+ #### Returns
429
+
430
+ ([`Awaitable<Next>`][awaitable]) The next promise or value
431
+
432
+ ### `Options<[T][, Next][, Args][, Self]>`
433
+
434
+ Options for chaining (`interface`).
435
+
436
+ ```ts
437
+ interface Options<
438
+ T = any,
439
+ Next = any,
440
+ Args extends readonly any[] = any[],
441
+ Self = any
442
+ > { /* ... */ }
443
+ ```
444
+
445
+ #### Type Parameters
446
+
447
+ - `T` (`any`, optional)
448
+ — the previously resolved value
449
+ - **default**: `any`
450
+ - `Next` (`any`, optional)
451
+ — the next resolved value
452
+ - **default**: `any`
453
+ - `Args` (`readonly any[]`, optional)
454
+ — the chain function arguments
455
+ - **default**: `any[]`
456
+ - `Self` (`any`, optional)
457
+ — the `this` context
458
+ - **default**: `any`
459
+
460
+ #### Properties
461
+
462
+ - `args?` (`Args` | `null` | `undefined`)
463
+ — the arguments to pass to the `chain` callback
464
+ - `chain` ([`Chain<T, Next, Args, Self>`][chain])
465
+ — the chain callback
466
+ - `context?` (`Self` | `null` | `undefined`)
467
+ — the `this` context of the `chain` and `reject` callbacks
468
+ - `reject?` ([`Reject<Next, any, Self>`][reject] | `null` | `undefined`)
469
+ — the callback to fire when a promise is rejected or an error is thrown
470
+
471
+ ### `Reject<[Next][, Fail][, Self]>`
472
+
473
+ The callback to fire when a promise is rejected or an error is thrown from a synchronous function (`type`).
474
+
475
+ ```ts
476
+ type Reject<
477
+ Next = any,
478
+ Fail = any,
479
+ Self = unknown
480
+ > = (this: Self, e: Fail) => Awaitable<Next>
481
+ ```
482
+
483
+ #### Type Parameters
484
+
485
+ - `Next` (`any`, optional)
486
+ — the next resolved value
487
+ - **default**: `any`
488
+ - `Fail` (`any`, optional)
489
+ — the error to handle
490
+ - **default**: `any`
491
+ - `Self` (`any`, optional)
492
+ — the `this` context
493
+ - **default**: `unknown`
494
+
495
+ #### Parameters
496
+
497
+ - **`this`** (`Self`)
498
+ - `e` (`Fail`)
499
+ — the error
500
+
501
+ #### Returns
502
+
503
+ ([`Awaitable<Next>`][awaitable]) The next promise or value
504
+
505
+ ## Glossary
506
+
507
+ ### *awaitable*
508
+
509
+ A synchronous or [*thenable*][thenable] value.
510
+
511
+ ### *thenable*
512
+
513
+ An object or function with a `.then` method.
514
+
515
+ JavaScript engines use duck-typing for promises.
516
+ Structures with a `.then` method will be treated as promise-like objects, and work with built-in mechanisms
517
+ like [`Promise.resolve`][promise-resolve] and the [`await` keyword][await] like native promises.
518
+
519
+ ## Project
520
+
521
+ ### Version
522
+
523
+ when adheres to [semver][].
524
+
525
+ ### Contribute
526
+
527
+ See [`CONTRIBUTING.md`](CONTRIBUTING.md).
528
+
529
+ This project has a [code of conduct](./CODE_OF_CONDUCT.md).
530
+ By interacting with this repository, organization, or community you agree to abide by its terms.
531
+
532
+ [await]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/await
533
+
534
+ [awaitable]: #awaitablet
535
+
536
+ [chain]: #chaint-next-args-self
537
+
538
+ [esm]: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c
539
+
540
+ [esmsh]: https://esm.sh
541
+
542
+ [isthenable]: #isthenabletvalue
543
+
544
+ [options]: #optionst-next-args-self
545
+
546
+ [promise-resolve]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve
547
+
548
+ [reject]: #rejectnext-fail-self
549
+
550
+ [semver]: https://semver.org
551
+
552
+ [thenable]: #thenable
553
+
554
+ [typescript]: https://www.typescriptlang.org
555
+
556
+ [when]: #whent-next-args-selfvalue-chain-reject-context-args
557
+
558
+ [yarn]: https://yarnpkg.com
@@ -0,0 +1,196 @@
1
+ /**
2
+ * @file Interfaces - Options
3
+ * @module when/interfaces/Options
4
+ */
5
+
6
+ /**
7
+ * Options for chaining.
8
+ *
9
+ * @template {any} [T=any]
10
+ * The previously resolved value
11
+ * @template {any} [Next=any]
12
+ * The next resolved value
13
+ * @template {ReadonlyArray<any>} [Args=any[]]
14
+ * The chain function arguments
15
+ * @template {any} [Self=any]
16
+ * The `this` context
17
+ */
18
+ interface Options<T = any, Next = any, Args extends readonly any[] = any[], Self = any> {
19
+ /**
20
+ * The arguments to pass to the {@linkcode chain} callback.
21
+ */
22
+ args?: Args | null | undefined;
23
+ /**
24
+ * The chain callback.
25
+ *
26
+ * @see {@linkcode Chain}
27
+ */
28
+ chain: Chain<T, Next, Args, Self>;
29
+ /**
30
+ * The `this` context of the {@linkcode chain}
31
+ * and {@linkcode reject} callbacks.
32
+ */
33
+ context?: Self | null | undefined;
34
+ /**
35
+ * The callback to fire when a promise is rejected or an error is thrown.
36
+ *
37
+ * @see {@linkcode Reject}
38
+ */
39
+ reject?: Reject<Next, any, Self> | null | undefined;
40
+ }
41
+
42
+ /**
43
+ * @file isThenable
44
+ * @module when/lib/isThenable
45
+ */
46
+ /**
47
+ * Check if `value` looks like a thenable.
48
+ *
49
+ * @template {any} T
50
+ * The resolved value
51
+ *
52
+ * @this {void}
53
+ *
54
+ * @param {unknown} value
55
+ * The thing to check
56
+ * @return {value is PromiseLike<T>}
57
+ * `true` if `value` is a thenable, `false` otherwise
58
+ */
59
+ declare function isThenable<T>(this: void, value: unknown): value is PromiseLike<T>;
60
+
61
+ /**
62
+ * @file when
63
+ * @module when/lib/when
64
+ */
65
+
66
+ /**
67
+ * Chain a callback, calling the function after `value` is resolved,
68
+ * or immediately if `value` is not thenable.
69
+ *
70
+ * @see {@linkcode Awaitable}
71
+ * @see {@linkcode Chain}
72
+ * @see {@linkcode Reject}
73
+ *
74
+ * @template {any} T
75
+ * The previously resolved value
76
+ * @template {any} [Next=any]
77
+ * The next resolved value
78
+ * @template {ReadonlyArray<any>} [Args=any[]]
79
+ * The chain function arguments
80
+ * @template {any} [Self=any]
81
+ * The `this` context
82
+ *
83
+ * @this {void}
84
+ *
85
+ * @param {Awaitable<T>} value
86
+ * The promise or the resolved value
87
+ * @param {Chain<T, Next, Args, Self>} chain
88
+ * The chain callback
89
+ * @param {Reject<Next, any, Self>} [reject]
90
+ * The callback to fire when a promise is rejected or an error is thrown
91
+ * @param {Self | null | undefined} [context]
92
+ * The `this` context of the chain and error callbacks
93
+ * @param {Args} args
94
+ * The arguments to pass to the chain callback
95
+ * @return {Awaitable<Next>}
96
+ * The next promise or value
97
+ */
98
+ declare function when<T, Next = any, Args extends any[] = any[], Self = unknown>(this: void, value: Awaitable<T>, chain: Chain<T, Next, Args, Self>, reject?: Reject<Next, any, Self> | null | undefined, context?: Self | null | undefined, ...args: Args): Awaitable<Next>;
99
+ /**
100
+ * Chain a callback, calling the function after `value` is resolved,
101
+ * or immediately if `value` is not thenable.
102
+ *
103
+ * @see {@linkcode Awaitable}
104
+ * @see {@linkcode Options}
105
+ *
106
+ * @template {any} T
107
+ * The previously resolved value
108
+ * @template {any} [Next=any]
109
+ * The next resolved value
110
+ * @template {ReadonlyArray<any>} [Args=any[]]
111
+ * The chain function arguments
112
+ * @template {any} [Self=unknown]
113
+ * The `this` context
114
+ *
115
+ * @this {void}
116
+ *
117
+ * @param {Awaitable<T>} value
118
+ * The promise or the resolved value
119
+ * @param {Options<T, Next, Args, Self>} chain
120
+ * Options for chaining
121
+ * @return {Awaitable<Next>}
122
+ * The next promise or value
123
+ */
124
+ declare function when<T, Next = any, Args extends any[] = any[], Self = unknown>(this: void, value: Awaitable<T>, chain: Options<T, Next, Args, Self>): Awaitable<Next>;
125
+
126
+ /**
127
+ * @file Type Aliases - Awaitable
128
+ * @module when/types/Awaitable
129
+ */
130
+ /**
131
+ * A synchronous or thenable value.
132
+ *
133
+ * @template {any} T
134
+ * The resolved value
135
+ */
136
+ type Awaitable<T> = PromiseLike<T> | T;
137
+
138
+ /**
139
+ * @file Type Aliases - Chain
140
+ * @module when/types/Chain
141
+ */
142
+
143
+ /**
144
+ * A chain callback.
145
+ *
146
+ * @see {@linkcode Awaitable}
147
+ *
148
+ * @template {any} [T=any]
149
+ * The previously resolved value
150
+ * @template {any} [Next=any]
151
+ * The next resolved value
152
+ * @template {ReadonlyArray<any>} [Args=any[]]
153
+ * The function arguments
154
+ * @template {any} [Self=unknown]
155
+ * The `this` context
156
+ *
157
+ * @this {Self}
158
+ *
159
+ * @param {[...Args, T]} params
160
+ * The function parameters, with the last being the previously resolved value.\
161
+ * In cases where a promise is not being resolved,
162
+ * this is the same `value` passed to `when`
163
+ * @return {Awaitable<Next>}
164
+ * The next promise or value
165
+ */
166
+ type Chain<T = any, Next = any, Args extends readonly any[] = any[], Self = unknown> = (this: Self, ...params: [...Args, T]) => Awaitable<Next>;
167
+
168
+ /**
169
+ * @file Type Aliases - Reject
170
+ * @module when/types/Reject
171
+ */
172
+
173
+ /**
174
+ * The callback to fire when a promise is rejected
175
+ * or an error is thrown from a synchronous function.
176
+ *
177
+ * @see {@linkcode Awaitable}
178
+ *
179
+ * @template {any} [Next=any]
180
+ * The next resolved value
181
+ * @template {any} [Fail=any]
182
+ * The error to handle
183
+ * @template {any} [Self=unknown]
184
+ * The `this` context
185
+ *
186
+ * @this {Self}
187
+ *
188
+ * @param {unknown} e
189
+ * The error
190
+ * @return {Awaitable<Next>}
191
+ * The next promise or value
192
+ */
193
+ type Reject<Next = any, Fail = any, Self = unknown> = (this: Self, e: Fail) => Awaitable<Next>;
194
+
195
+ export { when as default, isThenable as isPromiseLike, isThenable, when };
196
+ export type { Awaitable, Chain, Options, Reject };
package/dist/index.mjs ADDED
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @file Package Entry Point
3
+ * @module when
4
+ */
5
+ export * from '#lib/index';
6
+ export { default } from '#lib/when';
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @file Entry Point - Library
3
+ * @module when/lib
4
+ */
5
+ export { default as isPromiseLike, default as isThenable } from '#lib/is-thenable';
6
+ export { default as when } from '#lib/when';
@@ -0,0 +1,25 @@
1
+ /**
2
+ * @file isThenable
3
+ * @module when/lib/isThenable
4
+ */
5
+ /**
6
+ * Check if `value` looks like a thenable.
7
+ *
8
+ * @template {any} T
9
+ * The resolved value
10
+ *
11
+ * @this {void}
12
+ *
13
+ * @param {unknown} value
14
+ * The thing to check
15
+ * @return {value is PromiseLike<T>}
16
+ * `true` if `value` is a thenable, `false` otherwise
17
+ */
18
+ function isThenable(value) {
19
+ return (!Array.isArray(value) &&
20
+ typeof value === 'object' &&
21
+ value !== null &&
22
+ 'then' in value &&
23
+ typeof value.then === 'function');
24
+ }
25
+ export default isThenable;
@@ -0,0 +1,62 @@
1
+ /**
2
+ * @file when
3
+ * @module when/lib/when
4
+ */
5
+ import isThenable from '#lib/is-thenable';
6
+ export default when;
7
+ /**
8
+ * Chain a callback, calling the function after `value` is resolved,
9
+ * or immediately if `value` is not thenable.
10
+ *
11
+ * @see {@linkcode Chain}
12
+ * @see {@linkcode Options}
13
+ * @see {@linkcode Reject}
14
+ *
15
+ * @this {void}
16
+ *
17
+ * @param {unknown} value
18
+ * The promise or the resolved value
19
+ * @param {Chain<any, unknown> | Options} chain
20
+ * The chain callback or options for chaining
21
+ * @param {Reject | null | undefined} [reject]
22
+ * The callback to fire when a promise is rejected or an error is thrown
23
+ * @param {unknown} [context]
24
+ * The `this` context of the chain and error callbacks
25
+ * @param {unknown[]} args
26
+ * The arguments to pass to the chain callback
27
+ * @return {unknown}
28
+ * The next promise or value
29
+ */
30
+ function when(value, chain, reject, context, ...args) {
31
+ if (typeof chain === 'object') {
32
+ reject = chain.reject;
33
+ context = chain.context;
34
+ args = chain.args ?? [];
35
+ chain = chain.chain;
36
+ }
37
+ // no promise, call chain function immediately.
38
+ if (!isThenable(value)) {
39
+ try {
40
+ return chain.call(context, ...args, value);
41
+ }
42
+ catch (e) {
43
+ return fail(e);
44
+ }
45
+ }
46
+ // already have a promise, chain callback.
47
+ return value.then(resolved => chain.call(context, ...args, resolved), fail);
48
+ /**
49
+ * @this {void}
50
+ *
51
+ * @param {unknown} e
52
+ * The error to handle
53
+ * @return {unknown}
54
+ * The rejection result
55
+ * @throws {unknown}
56
+ */
57
+ function fail(e) {
58
+ if (typeof reject !== 'function')
59
+ throw e;
60
+ return reject.call(context, e);
61
+ }
62
+ }
package/package.json ADDED
@@ -0,0 +1,161 @@
1
+ {
2
+ "name": "@flex-development/when",
3
+ "description": "Like .then, but for synchronous values and thenables",
4
+ "version": "1.0.0",
5
+ "keywords": [
6
+ "async",
7
+ "await",
8
+ "awaitable",
9
+ "callback",
10
+ "chain",
11
+ "promise",
12
+ "sync",
13
+ "synchronous",
14
+ "then",
15
+ "thenable",
16
+ "typescript"
17
+ ],
18
+ "license": "BSD-3-Clause",
19
+ "homepage": "https://github.com/flex-development/when",
20
+ "repository": "https://github.com/flex-development/when.git",
21
+ "bugs": "https://github.com/flex-development/when/issues",
22
+ "author": {
23
+ "name": "Lexus Drumgold",
24
+ "url": "https://github.com/unicornware"
25
+ },
26
+ "publishConfig": {
27
+ "access": "public",
28
+ "diff-dst-prefix": "when",
29
+ "diff-src-prefix": "when",
30
+ "directory": "./",
31
+ "executableFiles": [],
32
+ "node-options": null,
33
+ "pack-destination": ".",
34
+ "parseable": true,
35
+ "prefer-dedupe": true,
36
+ "provenance": true,
37
+ "tag-version-prefix": ""
38
+ },
39
+ "type": "module",
40
+ "files": [
41
+ "CHANGELOG.md",
42
+ "LICENSE.md",
43
+ "README.md",
44
+ "dist"
45
+ ],
46
+ "exports": {
47
+ ".": {
48
+ "when": "./src/index.mts",
49
+ "default": "./dist/index.mjs"
50
+ },
51
+ "./package.json": "./package.json"
52
+ },
53
+ "imports": {
54
+ "#fixtures/*": "./__fixtures__/*.mts",
55
+ "#interfaces/*": {
56
+ "when": "./src/interfaces/*.mts",
57
+ "default": "./dist/interfaces/*.d.mts"
58
+ },
59
+ "#lib/*": {
60
+ "when": "./src/lib/*.mts",
61
+ "default": "./dist/lib/*.mjs"
62
+ },
63
+ "#tests/*": "./__tests__/*.mts",
64
+ "#types/*": {
65
+ "when": "./src/types/*.mts",
66
+ "default": "./dist/types/*.d.mts"
67
+ }
68
+ },
69
+ "module": "./dist/index.mjs",
70
+ "types": "./dist/index.d.mts",
71
+ "remarkConfig": {
72
+ "plugins": [
73
+ "@flex-development/remark-preset"
74
+ ]
75
+ },
76
+ "scripts": {
77
+ "build": "yarn clean:build && tsc -p tsconfig.build.json --noEmit false && yarn bundle:dts",
78
+ "bundle:dts": "rollup --config=rollup.config.mts && trash dist/{interfaces,types} && trash dist/{internal,lib}/*.d.mts",
79
+ "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
+ "check:format": "dprint check --incremental=false",
81
+ "check:lint": "eslint --exit-on-fatal-error --max-warnings 0 .",
82
+ "check:spelling": "cspell lint --color --no-progress --relative $@ \"**\"",
83
+ "check:types": "tsc -p tsconfig.json",
84
+ "check:types:attw": "yarn pack && attw package.tgz; yarn clean:pack",
85
+ "check:types:build": "tsc -p tsconfig.build.json",
86
+ "check:upgrades": "yarn upgrade-interactive",
87
+ "clean:build": "trash \"./{dist,*.tgz}\" || exit 0",
88
+ "clean:modules": "trash ./.yarn/{cache,*.gz} ./node_modules",
89
+ "clean:pack": "trash \"./*.tgz\"",
90
+ "clean:test": "trash ./coverage && trash __tests__/reports",
91
+ "codecov": "yarn test:cov && yarn test:cov:upload",
92
+ "codecov:validate": "cat .codecov.yml | curl --data-binary @- https://codecov.io/validate",
93
+ "commitlint": "commitlint -V",
94
+ "fix:cg": "yarn fix:format && yarn fix:lint",
95
+ "fix:dedupe": "yarn dedupe --strategy=highest",
96
+ "fix:format": "dprint fmt",
97
+ "fix:lint": "yarn check:lint --cache --fix",
98
+ "_postinstall": "[ -f ./.git ] && [ -f ./node_modules/.bin/husky ] && chmod +x .husky/_/* && husky || exit 0",
99
+ "postpack": "toggle-scripts +postinstall",
100
+ "postpublish": "toggle-scripts +prepack",
101
+ "prepack": "toggle-scripts -postinstall && yarn build",
102
+ "prepublishOnly": "toggle-scripts -prepack",
103
+ "release": "bash ./scripts/release.sh",
104
+ "remark": "remark .",
105
+ "test": "yarn clean:build; vitest run",
106
+ "test:cov": "yarn test --coverage",
107
+ "test:cov:reports": "yarn test:cov --merge-reports --mode=reports",
108
+ "test:cov:ui": "yarn test:ui --coverage",
109
+ "test:cov:upload": "./codecov -t $CODECOV_TOKEN -f ./coverage/lcov.info",
110
+ "test:reports": "yarn test --merge-reports --mode=reports",
111
+ "test:ui": "cross-env VITEST_UI=1 vitest --ui",
112
+ "typecheck": "yarn test --typecheck --mode=typecheck",
113
+ "typecheck:ui": "yarn test:ui --typecheck --mode=typecheck"
114
+ },
115
+ "devDependencies": {
116
+ "@arethetypeswrong/cli": "0.18.2",
117
+ "@commitlint/cli": "20.4.1",
118
+ "@commitlint/types": "20.4.0",
119
+ "@edge-runtime/vm": "5.0.0",
120
+ "@flex-development/commitlint-config": "1.0.1",
121
+ "@flex-development/eslint-config": "1.1.1",
122
+ "@flex-development/grease": "3.0.0-alpha.9",
123
+ "@flex-development/pathe": "4.0.2",
124
+ "@flex-development/remark-preset": "1.0.0",
125
+ "@flex-development/tutils": "6.0.0-alpha.25",
126
+ "@rollup/plugin-node-resolve": "16.0.3",
127
+ "@tsconfig/strictest": "2.0.8",
128
+ "@types/concat-stream": "2.0.3",
129
+ "@types/is-ci": "3.0.4",
130
+ "@types/node-notifier": "8.0.5",
131
+ "@types/rollup": "0.54.0",
132
+ "@vates/toggle-scripts": "1.0.0",
133
+ "@vitest/coverage-v8": "4.0.18",
134
+ "@vitest/ui": "4.0.18",
135
+ "concat-stream": "2.0.0",
136
+ "cross-env": "10.1.0",
137
+ "cspell": "9.6.4",
138
+ "devlop": "1.1.0",
139
+ "dprint": "0.51.1",
140
+ "editorconfig": "3.0.1",
141
+ "eslint": "9.39.2",
142
+ "growl": "1.10.5",
143
+ "happy-dom": "20.6.2",
144
+ "husky": "9.1.7",
145
+ "is-ci": "4.1.0",
146
+ "node-notifier": "10.0.1",
147
+ "pkg-size": "2.4.0",
148
+ "remark": "15.0.1",
149
+ "remark-cli": "12.0.1",
150
+ "rollup": "4.57.1",
151
+ "rollup-plugin-dts": "6.3.0",
152
+ "sh-syntax": "0.5.8",
153
+ "trash-cli": "7.2.0",
154
+ "typescript": "5.9.3",
155
+ "unified": "11.0.5",
156
+ "vfile": "6.0.3",
157
+ "vitest": "4.0.18"
158
+ },
159
+ "packageManager": "yarn@4.12.0",
160
+ "sideEffects": false
161
+ }