@flex-development/when 2.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/README.md CHANGED
@@ -28,15 +28,38 @@ like `.then`, but for synchronous values *and* thenables.
28
28
  - [Handle failures](#handle-failures)
29
29
  - [Bind `this` context](#bind-this-context)
30
30
  - [Use an options object](#use-an-options-object)
31
+ - [Integrate with `@typescript-eslint`](#integrate-with-typescript-eslint)
31
32
  - [API](#api)
32
- - [`isPromise<T>(value)`][ispromise]
33
+ - [`isCatchable<T>(value)`][iscatchable]
34
+ - [`isFinalizable<T>(value)`][isfinalizable]
35
+ - [`isPromise<T>(value[, finalizable])`][ispromise]
36
+ - [`isPromiseLike<T>(value)`][ispromiselike]
33
37
  - [`isThenable<T>(value)`][isthenable]
34
- - [`when<T[, Next][, Failure][, Args][, Error][, This]>(value, chain[, reject][, context][, ...args])`][when]
38
+ <!--lint disable-->
39
+ - [`when<T[, Next][, Failure][, Args][, Error][, This][, Result]>(value, chain[, fail][, context][, ...args])`][when]
40
+ <!--lint enable-->
41
+ - [Testing](#testing)
42
+ - [`createThenable<T[, Reason][, Result]>(executor[, options])`][createthenable]
35
43
  - [Types](#types)
36
44
  - [`Awaitable<T>`][awaitable]
45
+ - [`Catch<[T][, Reason]>`][catch]
46
+ - [`Catchable<[T]>`][catchable]
37
47
  - [`Chain<[T][, Next][, Args][, This]>`][chain]
38
- - [`Fail<[Next][, Error][, This]>`][fail]
48
+ - [`CreateThenableOptions`][createthenableoptions]
49
+ - [`Executor<[T][, Reason]>`][executor]
50
+ - [`Fail<[Next][, Reason][, This]>`][fail]
51
+ - [`Finalizable<[T]>`][finalizable]
52
+ - [`Finally<[T]>`][finally]
53
+ - [`Finish<[This]>`][finish]
54
+ - [`OnFinally`][onfinally]
55
+ - [`OnFulfilled<T[, Next]>`][onfulfilled]
56
+ - [`OnRejected<Next[, Reason]>`][onrejected]
39
57
  - [`Options<[T][, Next][, Failure][, Args][, Error][, This]>`][options]
58
+ - [`PromiseLike<T>`][promiselike]
59
+ - [`Reject<[Reason]>`][reject]
60
+ - [`Resolve<[T]>`][resolve]
61
+ - [`Then<[T][, Reason]>`][then]
62
+ - [`Thenable<[T]>`][thenable]
40
63
  - [Glossary](#glossary)
41
64
  - [Project](#project)
42
65
  - [Version](#version)
@@ -46,7 +69,7 @@ like `.then`, but for synchronous values *and* thenables.
46
69
  ## What is this?
47
70
 
48
71
  `when` is a tiny primitive for chaining callbacks
49
- onto [awaitables][awaitable] (synchronous or [*thenable*][thenable] values).
72
+ onto [awaitables][awaitable] (synchronous or [*thenable*][thenable-term] values).
50
73
 
51
74
  For thenable values, `then` is used to invoke the callback after resolution. Otherwise, the callback fires immediately.
52
75
  This makes it easy to write one code path that supports both synchronous values and promises.
@@ -55,7 +78,7 @@ This makes it easy to write one code path that supports both synchronous values
55
78
 
56
79
  `when` is especially useful in libraries implementing awaitable APIs.
57
80
 
58
- It provides [`Promise.then`][promise-then] semantics without forcing [`Promise.resolve`][promise-resolve],
81
+ It provides [`Promise.prototype.then`][promise-then] semantics without forcing [`Promise.resolve`][promise-resolve],
59
82
  preserving synchronous execution whenever possible.
60
83
 
61
84
  Typical use cases include plugin systems, hook pipelines, module resolvers, data loaders, and file system adapters where
@@ -79,10 +102,9 @@ when(value, fn) // only a promise if `value` is a thenable, or `fn` returns one
79
102
  ### Design guarantees
80
103
 
81
104
  - Synchronous values remain synchronous
82
- - [*Thenable*s][thenable] are chained without wrapping in [`Promise.resolve`][promise-resolve]
105
+ - [*Thenable*s][thenable-term] are chained directly without wrapping in [`Promise.resolve`][promise-resolve]
83
106
  - No additional microtasks are scheduled for non-thenables
84
107
  - Failures propagate unless a `fail` handler is provided
85
- - Returned thenables are preserved without additional wrapping
86
108
 
87
109
  ## Install
88
110
 
@@ -120,52 +142,52 @@ In browsers with [`esm.sh`][esmsh]:
120
142
  ### Chain a synchronous value
121
143
 
122
144
  ```ts
123
- import { isThenable, when, type Awaitable } from '@flex-development/when'
145
+ import { isThenable, when } from '@flex-development/when'
124
146
  import { ok } from 'devlop'
125
147
 
126
148
  /**
127
149
  * The result.
128
150
  *
129
- * @const {Awaitable<number>} result
151
+ * @const {number} result
130
152
  */
131
- const result: Awaitable<number> = when(0, n => n + 1)
153
+ const result: number = when(0, n => n + 1)
132
154
 
133
155
  ok(!isThenable(result), 'expected `result` to not be thenable')
134
156
  console.dir(result) // 1
135
157
  ```
136
158
 
137
- ### Chain a [*thenable*][thenable]
159
+ ### Chain a [*thenable*][thenable-term]
138
160
 
139
161
  ```ts
140
- import { isThenable, when, type Awaitable } from '@flex-development/when'
162
+ import { isPromise, when } from '@flex-development/when'
141
163
  import { ok } from 'devlop'
142
164
 
143
165
  /**
144
166
  * The result.
145
167
  *
146
- * @const {Awaitable<number>} result
168
+ * @const {Promise<number>} result
147
169
  */
148
- const result: Awaitable<number> = when(Promise.resolve(2), n => n + 1)
170
+ const result: Promise<number> = when(Promise.resolve(2), n => n + 1)
149
171
 
150
- ok(isThenable(result), 'expected `result` to be thenable')
172
+ ok(isPromise(result), 'expected `result` to be a promise')
151
173
  console.dir(await result) // 3
152
174
  ```
153
175
 
154
176
  ### Pass arguments to the chain callback
155
177
 
156
- When arguments are provided, they are passed to the `chain` callback first, followed by the resolved value.
178
+ When arguments are provided, they are passed to the [`chain`][chain] callback first, followed by the resolved value.
157
179
 
158
- When the `value` passed to `when` is not [*thenable*][thenable], the resolved value is the same `value`.
180
+ When the `value` passed to `when` is not a [*thenable*][thenable-term], the resolved value is the same `value`.
159
181
 
160
182
  ```ts
161
- import when, { type Awaitable } from '@flex-development/when'
183
+ import when from '@flex-development/when'
162
184
 
163
185
  /**
164
186
  * The result.
165
187
  *
166
- * @const {Awaitable<number>} result
188
+ * @const {number} result
167
189
  */
168
- const result: Awaitable<number> = when(
190
+ const result: number = when(
169
191
  1, // last argument passed to `Math.min`
170
192
  Math.min, // `chain`
171
193
  null, // `fail`
@@ -180,11 +202,11 @@ console.dir(result) // 1
180
202
 
181
203
  ### Handle failures
182
204
 
183
- For [*thenable*s][thenable], the `fail` callback is passed to `then` as the `onrejected` parameter,
184
- and if implemented, to `catch` as well to prevent unhandled rejections.
205
+ For [*thenable*s][thenable-term], the [`fail`][fail] callback is passed to [`then`][then] as the `onrejected` parameter,
206
+ and if implemented, to [`catch`][catch] as well to prevent unhandled rejections.
185
207
 
186
208
  ```ts
187
- import when, { type Awaitable } from '@flex-development/when'
209
+ import when from '@flex-development/when'
188
210
 
189
211
  /**
190
212
  * The thenable value.
@@ -198,9 +220,9 @@ const value: PromiseLike<never> = new Promise((resolve, reject) => {
198
220
  /**
199
221
  * The result.
200
222
  *
201
- * @const {Awaitable<boolean>} result
223
+ * @const {Promise<boolean>} result
202
224
  */
203
- const result: Awaitable<boolean> = when(value, chain, fail)
225
+ const result: Promise<boolean> = when(value, chain, fail)
204
226
 
205
227
  console.dir(await result) // false
206
228
 
@@ -230,7 +252,7 @@ function fail(this: void, e: Error): false {
230
252
  ### Bind `this` context
231
253
 
232
254
  ```ts
233
- import when, { type Awaitable } from '@flex-development/when'
255
+ import when from '@flex-development/when'
234
256
 
235
257
  /**
236
258
  * The `this` context.
@@ -240,9 +262,9 @@ type Context = { prefix: string }
240
262
  /**
241
263
  * The result.
242
264
  *
243
- * @const {Awaitable<string>} result
265
+ * @const {string} result
244
266
  */
245
- const result: Awaitable<string> = when(13, id, null, { prefix: 'id:' })
267
+ const result: string = when(13, id, null, { prefix: 'id:' })
246
268
 
247
269
  console.log(result) // 'id:13'
248
270
 
@@ -262,8 +284,6 @@ function id(this: Context, num: number | string): string {
262
284
  ### Use an options object
263
285
 
264
286
  ```ts
265
- import when, { type Awaitable } from '@flex-development/when'
266
-
267
287
  /**
268
288
  * The `this` context.
269
289
  */
@@ -279,9 +299,9 @@ const value: Promise<number> = new Promise(resolve => resolve(3))
279
299
  /**
280
300
  * The result.
281
301
  *
282
- * @const {Awaitable<number | undefined>} result
302
+ * @const {Promise<number | undefined>} result
283
303
  */
284
- const result: Awaitable<number | undefined> = when(value, {
304
+ const result: Promise<number | undefined> = when(value, {
285
305
  args: [39],
286
306
  chain: divide,
287
307
  context: { errors: [] },
@@ -316,19 +336,83 @@ function fail(this: Context, e: Error): undefined {
316
336
  }
317
337
  ```
318
338
 
339
+ ### Integrate with [`@typescript-eslint`][typescript-eslint]
340
+
341
+ ```js
342
+ /**
343
+ * The eslint configuration.
344
+ *
345
+ * @type {import('eslint').Linter.Config[]}
346
+ * @const config
347
+ */
348
+ const config = [
349
+ {
350
+ files: ['**/*.+(cjs|cts|js|jsx|mjs|mts|ts|tsx)'],
351
+ rules: {
352
+ '@typescript-eslint/promise-function-async': [
353
+ 2,
354
+ {
355
+ allowedPromiseNames: ['Thenable']
356
+ }
357
+ ]
358
+ }
359
+ }
360
+ ]
361
+
362
+ export default config
363
+ ```
364
+
319
365
  ## API
320
366
 
321
367
  `when` exports the identifiers listed below.
322
368
 
323
369
  The default export is [`when`][when].
324
370
 
325
- ### `isPromise<T>(value)`
371
+ ### `isCatchable<T>(value)`
372
+
373
+ Check if `value` looks like a [`Thenable`][thenable] that can be caught.
374
+
375
+ #### Type Parameters
376
+
377
+ - `T` (`any`)
378
+ — the resolved value
379
+
380
+ #### Parameters
381
+
382
+ - `value` (`unknown`)
383
+ — the thing to check
384
+
385
+ #### Returns
386
+
387
+ ([`value is Catchable<T>`][catchable])
388
+ `true` if `value` is a [*thenable*][thenable-term] with a [`catch`][catch] method, `false` otherwise
389
+
390
+ ### `isFinalizable<T>(value)`
391
+
392
+ Check if `value` looks like a [`Thenable`][thenable] that can be finalized.
393
+
394
+ #### Type Parameters
395
+
396
+ - `T` (`any`)
397
+ — the resolved value
398
+
399
+ #### Parameters
400
+
401
+ - `value` (`unknown`)
402
+ — the thing to check
403
+
404
+ #### Returns
405
+
406
+ ([`value is Finalizable<T>`][finalizable])
407
+ `true` if `value` is a [*thenable*][thenable-term] with a [`finally`][finally] method, `false` otherwise
408
+
409
+ ### `isPromise<T>(value[, finalizable])`
326
410
 
327
411
  Check if `value` looks like a `Promise`.
328
412
 
329
- > 👉 **Note**: This function intentionally performs a structural check instead of a brand check.
330
- > It does not rely on `instanceof Promise` or constructors, making it compatible with cross-realm promises
331
- > and custom thenables.
413
+ > 👉 **Note**: This function intentionally performs structural checks instead of brand checks.
414
+ > It does not rely on `instanceof Promise` or constructors, making it compatible with cross-realm
415
+ > promises and custom thenables.
332
416
 
333
417
  #### Type Parameters
334
418
 
@@ -339,16 +423,38 @@ Check if `value` looks like a `Promise`.
339
423
 
340
424
  - `value` (`unknown`)
341
425
  — the thing to check
426
+ - `finalizable` (`boolean` | `null` | `undefined`)
427
+ — whether a [`finally`][finally] method is required.\
428
+ when `false`, only [`then`][then] and [`catch`][catch] are checked
342
429
 
343
430
  #### Returns
344
431
 
345
- (`value is Promise<T>`) `true` if `value` is a [*thenable*][thenable] with a `catch` method, `false` otherwise
432
+ (`value is Promise<T>`)
433
+ `true` if `value` is a [*thenable*][thenable-term] with a [`catch`][catch] method,
434
+ and [`finally`][finally] method (if requested), `false` otherwise
346
435
 
347
- ### `isThenable<T>(value)`
436
+ ### `isPromiseLike<T>(value)`
437
+
438
+ Check if `value` looks like a [`PromiseLike`][promiselike] structure.
348
439
 
349
- Check if `value` looks like a [*thenable*][thenable], i.e. a `PromiseLike` object.
440
+ #### Type Parameters
350
441
 
351
- > 👉 **Note**: Also exported as `isPromiseLike`.
442
+ - `T` (`any`)
443
+ — the resolved value
444
+
445
+ #### Parameters
446
+
447
+ - `value` (`unknown`)
448
+ — the thing to check
449
+
450
+ #### Returns
451
+
452
+ ([`value is PromiseLike<T>`][promiselike])
453
+ `true` if `value` is an object or function with a [`then`][then] method, `false` otherwise
454
+
455
+ ### `isThenable<T>(value)`
456
+
457
+ Check if `value` looks like a [*thenable*][thenable-term].
352
458
 
353
459
  #### Type Parameters
354
460
 
@@ -362,16 +468,17 @@ Check if `value` looks like a [*thenable*][thenable], i.e. a `PromiseLike` objec
362
468
 
363
469
  #### Returns
364
470
 
365
- (`value is PromiseLike<T>`) `true` if `value` is an object or function with a `then` method, `false` otherwise
471
+ ([`value is Thenable<T>`][thenable]) `true` if `value` is an object or function with a [`then`][then] method,
472
+ and maybe-callable methods [`catch`][catch] and/or [`finally`][finally], `false` otherwise
366
473
 
367
474
  <!--lint disable-->
368
475
 
369
- ### `when<T[, Next][, Failure][, Args][, Error][, This]>(value, chain[, fail][, context][, ...args])`
476
+ ### `when<T[, Next][, Failure][, Args][, Error][, This][, Result]>(value, chain[, fail][, context][, ...args])`
370
477
 
371
478
  <!--lint enable-->
372
479
 
373
480
  Chain a callback, calling the function after `value` is resolved,
374
- or immediately if `value` is not a [*thenable*][thenable].
481
+ or immediately if `value` is not a [*thenable*][thenable-term].
375
482
 
376
483
  #### Overloads
377
484
 
@@ -380,7 +487,8 @@ function when<
380
487
  T,
381
488
  Next = any,
382
489
  Args extends any[] = any[],
383
- This = unknown
490
+ This = unknown,
491
+ Result extends Awaitable<Next> = Awaitable<Next>
384
492
  >(
385
493
  this: void,
386
494
  value: Awaitable<T>,
@@ -388,7 +496,7 @@ function when<
388
496
  fail?: null | undefined,
389
497
  context?: This | null | undefined,
390
498
  ...args: Args
391
- ): Awaitable<Next>
499
+ ): Result
392
500
  ```
393
501
 
394
502
  ```ts
@@ -398,7 +506,8 @@ function when<
398
506
  Failure = Next,
399
507
  Args extends any[] = any[],
400
508
  Error = any,
401
- This = unknown
509
+ This = unknown,
510
+ Result extends Awaitable<Failure | Next> = Awaitable<Failure | Next>
402
511
  >(
403
512
  this: void,
404
513
  value: Awaitable<T>,
@@ -406,7 +515,7 @@ function when<
406
515
  fail?: Fail<Failure, Error, This> | null | undefined,
407
516
  context?: This | null | undefined,
408
517
  ...args: Args
409
- ): Awaitable<Failure | Next>
518
+ ): Result
410
519
  ```
411
520
 
412
521
  ```ts
@@ -416,12 +525,13 @@ function when<
416
525
  Failure = Next,
417
526
  Args extends any[] = any[],
418
527
  Error = any,
419
- This = unknown
528
+ This = unknown,
529
+ Result extends Awaitable<Failure | Next> = Awaitable<Failure | Next>
420
530
  >(
421
531
  this: void,
422
532
  value: Awaitable<T>,
423
533
  chain: Options<T, Next, Failure, Args, Error, This>
424
- ): Awaitable<Failure | Next>
534
+ ): Result
425
535
  ```
426
536
 
427
537
  #### Type Parameters
@@ -443,6 +553,9 @@ function when<
443
553
  - `This` (`any`, optional)
444
554
  — the `this` context
445
555
  - **default**: `unknown`
556
+ - `Result` ([`Awaitable<Failure | Next>`][awaitable], optional)
557
+ — the next awaitable
558
+ - **default**: [`Awaitable<Failure | Next>`][awaitable]
446
559
 
447
560
  #### Parameters
448
561
 
@@ -452,7 +565,7 @@ function when<
452
565
  — the chain callback or options for chaining
453
566
  - `fail` ([`Fail<Failure, Error, This>`][fail] | `null` | `undefined`)
454
567
  — the callback to fire when a failure occurs. failures include:
455
- - rejections of the input [*thenable*][thenable]
568
+ - rejections of the input [*thenable*][thenable-term]
456
569
  - rejections returned from `chain`
457
570
  - synchronous errors thrown in `chain`\
458
571
  if no `fail` handler is provided, failures are re-thrown or re-propagated.
@@ -467,16 +580,78 @@ function when<
467
580
 
468
581
  ([`Awaitable<Failure | Next>`][awaitable] | [`Awaitable<Next>`][awaitable]) The next [*awaitable*][awaitable-term]
469
582
 
583
+ ## Testing
584
+
585
+ Test utilities are exported from `@flex-development/when/testing`.
586
+
587
+ There is no default export.
588
+
589
+ ```ts
590
+ import {
591
+ isCatchable,
592
+ isFinalizable,
593
+ type Thenable
594
+ } from '@flex-development/when'
595
+ import { createThenable } from '@flex-development/when/testing'
596
+ import { ok } from 'devlop'
597
+
598
+ /**
599
+ * The thenable.
600
+ *
601
+ * @const {Thenable<number>} thenable
602
+ */
603
+ const thenable: Thenable<number> = createThenable(resolve => resolve(10))
604
+
605
+ ok(isCatchable(thenable), 'expected `thenable` to be a catchable')
606
+ ok(isFinalizable(thenable), 'expected `thenable` to be a finalizable')
607
+
608
+ console.dir(await thenable.then(value => value + 3)) // 13
609
+ ```
610
+
611
+ ### `createThenable<T[, Reason][, Result]>(executor[, options])`
612
+
613
+ Create a thenable.
614
+
615
+ The returned object conforms to [`Thenable`][thenable] and ensures [`then`][then] always returns another `Thenable`,
616
+ even when adopting a foreign [*thenable*][thenable-term].
617
+
618
+ When `options` is omitted, `null`, or `undefined`, the returned thenable is *modern* (a [*thenable*][thenable-term]
619
+ with [`then`][then], [`catch`][catch], and [`finally`][finally] methods).
620
+ Pass an options object (e.g. `{}`) to start from a *bare* ([`then`][then] method only) thenable
621
+ and selectively enable methods.
622
+
623
+ #### Type Parameters
624
+
625
+ - `T` (`any`)
626
+ — the resolved value
627
+ - `Reason` (`any`, optional)
628
+ — the reason for a rejection
629
+ - **default**: `Error`
630
+ - `Result` ([`Thenable<T>`][thenable], optional)
631
+ — the thenable
632
+ - **default**: [`Thenable<T>`][thenable]
633
+
634
+ #### Parameters
635
+
636
+ - `executor` ([`Executor<T, Reason>`][executor])
637
+ — the initialization callback
638
+ - `options` ([`CreateThenableOptions`][createthenableoptions] | `null` | `undefined`, optional)
639
+ — options for creating a thenable
640
+
641
+ #### Returns
642
+
643
+ (`Result`) The [*thenable*][thenable-term]
644
+
470
645
  ## Types
471
646
 
472
647
  This package is fully typed with [TypeScript][].
473
648
 
474
649
  ### `Awaitable<T>`
475
650
 
476
- A synchronous or [*thenable*][thenable] value (`type`).
651
+ A synchronous or [*thenable*][thenable-term] value (`type`).
477
652
 
478
653
  ```ts
479
- type Awaitable<T> = PromiseLike<T> | T
654
+ type Awaitable<T> = Thenable<T> | T
480
655
  ```
481
656
 
482
657
  #### Type Parameters
@@ -484,6 +659,57 @@ type Awaitable<T> = PromiseLike<T> | T
484
659
  - `T` (`any`)
485
660
  — the resolved value
486
661
 
662
+ ### `Catch<[T][, Reason]>`
663
+
664
+ Attach a callback only for the rejection of a [`Thenable`][thenable] (`type`).
665
+
666
+ ```ts
667
+ type Catch<T = unknown, Reason = any> = <Next = never>(
668
+ this: any,
669
+ onrejected?: OnRejected<Next, Reason> | null | undefined
670
+ ) => Thenable<Next | T>
671
+ ```
672
+
673
+ #### Type Parameters
674
+
675
+ - `T` (`any`, optional)
676
+ — the resolved value
677
+ - **default**: `unknown`
678
+ - `Reason` (`any`, optional)
679
+ — the reason for the rejection
680
+ - **default**: `any`
681
+ - `Next` (`any`, optional)
682
+ — the next resolved value
683
+ - **default**: `never`
684
+
685
+ #### Parameters
686
+
687
+ - `onrejected` ([`OnRejected<Next, Reason>`][onrejected] | `null` | `undefined`)
688
+ — the callback to execute when the thenable is rejected
689
+
690
+ #### Returns
691
+
692
+ ([`Thenable<Next | T>`][thenable]) The next [*thenable*][thenable-term]
693
+
694
+ ### `Catchable<[T]>`
695
+
696
+ A [`Thenable`][thenable] that can be caught (`interface`).
697
+
698
+ #### Extends
699
+
700
+ - [`Thenable<T>`][thenable]
701
+
702
+ #### Type Parameters
703
+
704
+ - `T` (`any`, optional)
705
+ — the resolved value
706
+ - **default**: `any`
707
+
708
+ #### Properties
709
+
710
+ - `catch` ([`Catch<T>`][catch])
711
+ — attach a callback only to be invoked on rejection
712
+
487
713
  ### `Chain<[T][, Next][, Args][, This]>`
488
714
 
489
715
  A chain callback (`type`).
@@ -523,16 +749,71 @@ type Chain<
523
749
 
524
750
  ([`Awaitable<Next>`][awaitable]) The next [*awaitable*][awaitable-term]
525
751
 
526
- ### `Fail<[Next][, Error][, This]>`
752
+ ### `CreateThenableOptions`
753
+
754
+ Options for creating a [*thenable*][thenable-term] (`interface`).
755
+
756
+ > 👉 **Note**: Exported from `@flex-development/when/testing` only.
757
+
758
+ #### Properties
759
+
760
+ - `catch?` (`boolean` | `null` | `undefined`)
761
+ — control whether returned thenables implement a [`catch`][catch] method.\
762
+ when an options object is omitted, `null`, or `undefined`, the method will be implemented.\<br/>
763
+ when an options object is provided, `catch` is only implemented if `options.catch` is `true`.\
764
+ if `options.catch` is `null` or `undefined`, the thenable's `catch` property will have the same value.\
765
+ pass `false` to disable the method implementation
766
+ - `finally?` (`boolean` | `null` | `undefined`)
767
+ — control whether returned thenables implement a [`finally`][finally] method.\
768
+ when an options object is omitted, `null`, or `undefined`, the method will be implemented.\
769
+ when an options object is provided, `finally` is only implemented if `options.finally` is `true`.\
770
+ if `options.finally` is `null` or `undefined`, the thenable's `finally` property will have the same value.\
771
+ pass `false` to disable the method implementation
772
+
773
+ ### `Executor<[T][, Reason]>`
774
+
775
+ The callback used to initialize a [*thenable*][thenable-term] (`type`).
776
+
777
+ > 👉 **Note**: Exported from `@flex-development/when/testing` only.
778
+
779
+ ```ts
780
+ type Executor<T = any, Reason = Error> = (
781
+ this: void,
782
+ resolve: Resolve<T>,
783
+ reject: Reject<Reason>
784
+ ) => undefined | void
785
+ ```
786
+
787
+ #### Type Parameters
788
+
789
+ - `T` (`any`, optional)
790
+ — the resolved value
791
+ - **default**: `any`
792
+ - `Reason` (`any`, optional)
793
+ — the reason for a rejection
794
+ - **default**: `Error`
795
+
796
+ #### Parameters
797
+
798
+ - `resolve` ([`Resolve<T>`][resolve])
799
+ — the callback used to resolve the thenable with a value or the result of another [*awaitable*][awaitable-term]
800
+ - `reject` ([`Reject<Reason>`][reject])
801
+ — the callback used to reject the thenable with a provided reason or error
802
+
803
+ #### Returns
804
+
805
+ (`undefined` | `void`) Nothing
806
+
807
+ ### `Fail<[Next][, Reason][, This]>`
527
808
 
528
809
  The callback to fire when a failure occurs (`type`).
529
810
 
530
811
  ```ts
531
812
  type Fail<
532
813
  Next = any,
533
- Error = any,
814
+ Reason = any,
534
815
  This = unknown
535
- > = (this: This, e: Error) => Awaitable<Next>
816
+ > = (this: This, reason: Reason) => Awaitable<Next>
536
817
  ```
537
818
 
538
819
  #### Type Parameters
@@ -540,8 +821,8 @@ type Fail<
540
821
  - `Next` (`any`, optional)
541
822
  — the next resolved value
542
823
  - **default**: `any`
543
- - `Error` (`any`, optional)
544
- — the error to handle
824
+ - `Reason` (`any`, optional)
825
+ — the reason for the failure
545
826
  - **default**: `any`
546
827
  - `This` (`any`, optional)
547
828
  — the `this` context
@@ -550,8 +831,141 @@ type Fail<
550
831
  #### Parameters
551
832
 
552
833
  - **`this`** (`This`)
553
- - `e` (`Error`)
554
- — the error
834
+ - `reason` (`Reason`)
835
+ — the reason for the failure
836
+
837
+ #### Returns
838
+
839
+ ([`Awaitable<Next>`][awaitable]) The next [*awaitable*][awaitable-term]
840
+
841
+ ### `Finalizable<[T]>`
842
+
843
+ A [`Thenable`][thenable] that can be finalized (`interface`).
844
+
845
+ #### Extends
846
+
847
+ - [`Thenable<T>`][thenable]
848
+
849
+ #### Type Parameters
850
+
851
+ - `T` (`any`, optional)
852
+ — the resolved value
853
+ - **default**: `any`
854
+
855
+ #### Properties
856
+
857
+ - `finally` ([`Finally<T>`][finally])
858
+ — attach a callback only to be invoked on settlement (fulfillment or rejection)
859
+ > 👉 **note**: the resolved value cannot be modified from the callback
860
+
861
+ ### `Finally<[T]>`
862
+
863
+ Attach a callback that is invoked only when a [`Thenable`][thenable] is settled (fulfilled or rejected) (`type`).
864
+
865
+ ```ts
866
+ type Finally<T = unknown> = (
867
+ this: any,
868
+ onfinally?: OnFinally | null | undefined
869
+ ) => Thenable<T>
870
+ ```
871
+
872
+ #### Type Parameters
873
+
874
+ - `T` (`any`, optional)
875
+ — the resolved value
876
+ - **default**: `unknown`
877
+
878
+ #### Parameters
879
+
880
+ - `onfinally` ([`OnFinally`][onfinally] | `null` | `undefined`)
881
+ — the callback to execute when the thenable is settled
882
+
883
+ #### Returns
884
+
885
+ ([`Thenable<T>`][thenable]) The next [*thenable*][thenable-term]
886
+
887
+ ### `Finish<[This]>`
888
+
889
+ A post-processing hook invoked exactly once after an [*awaitable*][awaitable-term] settles,
890
+ regardless of success or failure (`type`).
891
+
892
+ The resolved value cannot be modified from the hook, and any error is re-thrown after execution.
893
+
894
+ ```ts
895
+ type Finish<This = unknown> = (this: This) => undefined | void
896
+ ```
897
+
898
+ #### Type Parameters
899
+
900
+ - `This` (`any`, optional)
901
+ — the `this` context
902
+ - **default**: `unknown`
903
+
904
+ #### Returns
905
+
906
+ (`undefined` | `void`) Nothing
907
+
908
+ ### `OnFinally`
909
+
910
+ The callback to execute when a [`Thenable`][thenable] is settled (fulfilled or rejected) (`type`).
911
+
912
+ ```ts
913
+ type OnFinally = (this: unknown) => undefined | void
914
+ ```
915
+
916
+ #### Returns
917
+
918
+ (`undefined` | `void`) Nothing
919
+
920
+ ### `OnFulfilled<T[, Next]>`
921
+
922
+ The callback to execute when a [`Thenable`][thenable] is resolved (`type`).
923
+
924
+ ```ts
925
+ type OnFulfilled<T, Next = T> = (this: unknown, value: T) => Awaitable<Next>
926
+ ```
927
+
928
+ #### Type Parameters
929
+
930
+ - `T` (`any`)
931
+ — the resolved value
932
+ - `Next` (`any`, optional)
933
+ — the next resolved value
934
+ - **default**: `T`
935
+
936
+ #### Parameters
937
+
938
+ - `value` (`T`)
939
+ — the resolved value
940
+
941
+ #### Returns
942
+
943
+ ([`Awaitable<Next>`][awaitable]) The next [*awaitable*][awaitable-term]
944
+
945
+ ### `OnRejected<Next[, Reason]>`
946
+
947
+ The callback to execute when a [`Thenable`][thenable] is rejected (`type`).
948
+
949
+ ```ts
950
+ type OnRejected<
951
+ Next,
952
+ Reason = any
953
+ > = (this: unknown, reason: Reason) => Awaitable<Next>
954
+ ```
955
+
956
+ #### Type Parameters
957
+
958
+ - `Next` (`any`, optional)
959
+ — the next resolved value
960
+ - **default**: `any`
961
+ - `Reason` (`any`, optional)
962
+ — the reason for the rejection
963
+ - **default**: `any`
964
+
965
+ #### Parameters
966
+
967
+ - `reason` (`Reason`)
968
+ — the reason for the rejection
555
969
 
556
970
  #### Returns
557
971
 
@@ -603,29 +1017,163 @@ interface Options<
603
1017
  — the `this` context of the `chain` and `fail` callbacks
604
1018
  - `fail?` ([`Fail<Next, Error, This>`][fail] | `null` | `undefined`)
605
1019
  — the callback to fire when a failure occurs. failures include:
606
- - rejections of the input [*thenable*][thenable]
1020
+ - rejections of the input [*thenable*][thenable-term]
607
1021
  - rejections returned from `chain`
608
1022
  - synchronous errors thrown in `chain`\
609
1023
  if no `fail` handler is provided, failures are re-thrown or re-propagated.
610
1024
  > 👉 **note**: for thenables, this callback is passed to `then` as the `onrejected` parameter,
611
- > and if implemented, to `catch` as well to prevent unhandled rejections.
1025
+ > and if implemented, to [`catch`][catch] as well to prevent unhandled rejections.
1026
+ - `finish?` ([`Finish<This>`][finish] | `null` | `undefined`)
1027
+ — the callback to invoke after chaining completes, whether the operation succeeds or fails.\
1028
+ it runs exactly once after `chain` and `fail`, cannot affect the resolved value, and does not intercept errors
1029
+
1030
+ ### `PromiseLike<T>`
1031
+
1032
+ To ensure native `Promise` and `PromiseLike` are assignable to [`Thenable`][thenable],
1033
+ `when` ships a small global augmentation for `PromiseLike`.
1034
+
1035
+ No new methods or overloads are introduced — the `then` signature is rewritten to match
1036
+ the official [TypeScript][] lib definition (as in `lib.es2015.d.ts`).
1037
+
1038
+ This is required for both compatibility, and type inference when mixing `Thenable` with built-in promise types.
1039
+
1040
+ #### Type Parameters
1041
+
1042
+ - `T` (`any`)
1043
+ — the resolved value
1044
+
1045
+ ### `Reject<[Reason]>`
1046
+
1047
+ The callback used to reject a [*thenable*][thenable-term] with a provided reason or error (`type`).
1048
+
1049
+ > 👉 **Note**: Exported from `@flex-development/when/testing` only.
1050
+
1051
+ ```ts
1052
+ type Reject<Reason = Error> = (this: void, reason: Reason) => undefined
1053
+ ```
1054
+
1055
+ #### Type Parameters
1056
+
1057
+ - `Reason` (`any`, optional)
1058
+ — the reason for the rejection
1059
+ - **default**: `Error`
1060
+
1061
+ #### Parameters
1062
+
1063
+ - `reason` (`Reason`)
1064
+ — the reason for the rejection
1065
+
1066
+ #### Returns
1067
+
1068
+ (`undefined`) Nothing
1069
+
1070
+ ### `Resolve<[T]>`
1071
+
1072
+ The callback used to resolve a [*thenable*][thenable-term] with a value
1073
+ or the result of another [*awaitable*][awaitable-term] (`type`).
1074
+
1075
+ > 👉 **Note**: Exported from `@flex-development/when/testing` only.
1076
+
1077
+ ```ts
1078
+ type Resolve<T = any> = (this: void, value: Awaitable<T>) => undefined
1079
+ ```
1080
+
1081
+ #### Type Parameters
1082
+
1083
+ - `T` (`any`, optional)
1084
+ — the resolved value
1085
+ - **default**: `any`
1086
+
1087
+ #### Parameters
1088
+
1089
+ - `value` ([`Awaitable<T>`][awaitable])
1090
+ — the awaitable
1091
+
1092
+ #### Returns
1093
+
1094
+ (`undefined`) Nothing
1095
+
1096
+ ### `Then<T[, Reason]>`
1097
+
1098
+ Attach callbacks for the resolution and/or rejection of a [`Thenable`][thenable] (`type`).
1099
+
1100
+ ```ts
1101
+ type Then<T = unknown, Reason = any> = <Succ = T, Fail = never>(
1102
+ this: any,
1103
+ onfulfilled?: OnFulfilled<T, Succ> | null | undefined,
1104
+ onrejected?: OnRejected<Fail, Reason> | null | undefined
1105
+ ) => Thenable<Fail | Succ>
1106
+ ```
1107
+
1108
+ #### Type Parameters
1109
+
1110
+ - `T` (`any`, optional)
1111
+ — the previously resolved value
1112
+ - **default**: `unknown`
1113
+ - `Reason` (`any`, optional)
1114
+ — the reason for a rejection
1115
+ - **default**: `any`
1116
+ - `Succ` (`any`, optional)
1117
+ — the next resolved value on success
1118
+ - **default**: `T`
1119
+ - `Fail` (`any`, optional)
1120
+ — the next resolved value on failure
1121
+ - **default**: `never`
1122
+
1123
+ #### Parameters
1124
+
1125
+ - `onfulfilled` ([`OnFulfilled<T, Succ>`][onfulfilled] | `null` | `undefined`)
1126
+ — the callback to execute when the thenable is resolved
1127
+ - `onrejected` ([`OnRejected<Fail, Reason>`][onrejected] | `null` | `undefined`)
1128
+ — the callback to execute when the thenable is rejected
1129
+
1130
+ #### Returns
1131
+
1132
+ ([`Thenable<Fail | Succ>`][thenable]) The next [*thenable*][thenable-term]
1133
+
1134
+ ### `Thenable<[T]>`
1135
+
1136
+ The completion of an asynchronous operation, and the minimal structural contract required
1137
+ by [`when`][when] to treat a value as asynchronous (`interface`).
1138
+
1139
+ Unlike `PromiseLike`, this interface allows a maybe-callable [`catch`][catch] method, which when present,
1140
+ is used by [`when`][when] to ensure failures are handled without forcing promise allocation.
1141
+
1142
+ Maybe-callable methods are named so because they are not required,
1143
+ and may be a method implementation, `null`, or `undefined`.
1144
+
1145
+ #### Type Parameters
1146
+
1147
+ - `T` (`any`, optional)
1148
+ — the resolved value
1149
+ - **default**: `any`
1150
+
1151
+ #### Properties
1152
+
1153
+ - `catch?` ([`Catch<T>`][catch] | `null` | `undefined`)
1154
+ — attach a callback only to be invoked on rejection
1155
+ - `then` ([`Then<T>`][then])
1156
+ — attach callbacks to be invoked on resolution (fulfillment) and/or rejection
1157
+ - `finally?` ([`Finally<T>`][finally] | `null` | `undefined`)
1158
+ — attach a callback only to be invoked on settlement (fulfillment or rejection)
1159
+ > 👉 **note**: the resolved value cannot be modified from the callback
612
1160
 
613
1161
  ## Glossary
614
1162
 
615
1163
  ### *awaitable*
616
1164
 
617
- A synchronous or [*thenable*][thenable] value.
1165
+ A synchronous or [*thenable*][thenable-term] value.
618
1166
 
619
1167
  ### *thenable*
620
1168
 
621
- An object or function with a `then` method.
1169
+ An object or function with a [`then`][then] method.
622
1170
 
623
1171
  JavaScript engines use duck-typing for promises.
624
1172
  Arrays, functions, and objects with a `then` method will be treated as promise-like objects, and work with built-in
625
1173
  mechanisms like [`Promise.resolve`][promise-resolve] and the [`await` keyword][await] like native promises.
626
1174
 
627
- Some thenables also implement a `catch` method (like native promises).
628
- When available, `when` uses it to ensure rejections are handled.
1175
+ Some thenables also implement a [`catch`][catch] method (like native promises).
1176
+ When available, [`when`][when] uses it to ensure rejections are handled.
629
1177
 
630
1178
  ## Project
631
1179
 
@@ -653,30 +1201,70 @@ Support long-term stability by sponsoring Flex Development.
653
1201
 
654
1202
  [awaitable]: #awaitablet
655
1203
 
1204
+ [catch]: #catcht-reason
1205
+
1206
+ [catchable]: #catchablet
1207
+
656
1208
  [chain]: #chaint-next-args-this
657
1209
 
1210
+ [createthenable]: #createthenablet-reason-resultexecutor-options
1211
+
1212
+ [createthenableoptions]: #createthenableoptions
1213
+
658
1214
  [esm]: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c
659
1215
 
660
1216
  [esmsh]: https://esm.sh
661
1217
 
662
- [fail]: #failnext-error-this
1218
+ [executor]: #executort-reason
1219
+
1220
+ [fail]: #failnext-reason-this
1221
+
1222
+ [finalizable]: #finalizablet
1223
+
1224
+ [finally]: #finallyt
1225
+
1226
+ [finish]: #finishthis
1227
+
1228
+ [iscatchable]: #iscatchabletvalue
1229
+
1230
+ [isfinalizable]: #isfinalizabletvalue
663
1231
 
664
- [ispromise]: #ispromisetvalue
1232
+ [ispromise]: #ispromisetvalue-finalizable
1233
+
1234
+ [ispromiselike]: #ispromiseliketvalue
665
1235
 
666
1236
  [isthenable]: #isthenabletvalue
667
1237
 
1238
+ [onfinally]: #onfinally
1239
+
1240
+ [onfulfilled]: #onfulfilledt-next
1241
+
1242
+ [onrejected]: #onrejectednext-reason
1243
+
668
1244
  [options]: #optionst-next-failure-args-error-this
669
1245
 
670
1246
  [promise-resolve]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve
671
1247
 
672
1248
  [promise-then]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise/then
673
1249
 
1250
+ [promiselike]: #promiseliket
1251
+
1252
+ [reject]: #rejectreason
1253
+
1254
+ [resolve]: #resolvet
1255
+
674
1256
  [semver]: https://semver.org
675
1257
 
676
- [thenable]: #thenable
1258
+ [then]: #thent-reason
1259
+
1260
+ [thenable-term]: #thenable
1261
+
1262
+ [thenable]: #thenablet
1263
+
1264
+ [typescript-eslint]: https://typescript-eslint.io
677
1265
 
678
1266
  [typescript]: https://www.typescriptlang.org
679
1267
 
680
- [when]: #whent-next-failure-args-error-thisvalue-chain-fail-context-args
1268
+ [when]: #whent-next-failure-args-error-this-resultvalue-chain-fail-context-args
681
1269
 
682
1270
  [yarn]: https://yarnpkg.com