Skip to content

Commit

Permalink
Fix capture function to preserve type identity (#4)
Browse files Browse the repository at this point in the history
* Add a test that demonstrates the issue with `capture` function

The `capture` function should pass-through any type it is given,
in fact, it should act as an identity-function from the type perspective.

* The `capture` function passes through the type correctly.

It should act as an identity function from type perspective.

The use-case is that CancellablePromise might be enhanced with some additional
information, such as progress reporting. We need the enhancements
to be preserved when the promise is passed through the `capture` function.

Before the fix, the return type from `capture` would be a bare
CancellablePromise<T>, hiding any additional features of the particular
type that inherited from CancellablePromise<T>.

* Remove nested block to make linter hapy

The nested block was there for documentation purposes.

* Fix test issue "Exceeded timeout of 5000 ms for a test."

* Run Prettier

Co-authored-by: Ondrej Stanek <ondrej@ostan.cz>
Co-authored-by: Sam Magura <srmagura@gmail.com>
  • Loading branch information
3 people authored Jan 23, 2023
1 parent 78527fc commit ee4eb6b
Show file tree
Hide file tree
Showing 2 changed files with 33 additions and 4 deletions.
29 changes: 29 additions & 0 deletions src/__tests__/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,32 @@ describe('buildCancellablePromise', () => {
await expect(p).rejects.toThrow(error)
})
})

describe('buildCancellablePromise capture', () => {
it('passes through the argument type', async () => {
jest.useRealTimers()
// this is a "compile-time" test
// it will only be tested when compiled with TypeScript (`$ yarn tsc`)
const promise = buildCancellablePromise(async (capture) => {
// we build two promises that we enhance with some additional fields
const fancyPromise1 = Object.assign(getPromise('1'), {
reportProgress: () => 0.9,
})
const fancyPromise2 = Object.assign(getPromise('2'), {
info: 'some enhanced promise',
})
const capturedFancyPromise1 = capture(fancyPromise1)
const capturedFancyPromise2 = capture(fancyPromise2)
// these will throw a compile time error if `capture` is not an identity function (from type perspective):
// the field `reportProgress` should be accessible on the type that passes through the `caputure` function
expect(capturedFancyPromise1.reportProgress()).toBe(0.9)
// the `info` field should be accessible even if the promise is passed through the `capture` function
expect(capturedFancyPromise2.info).toBe('some enhanced promise')

return [await capturedFancyPromise1, await capturedFancyPromise2]
})
const [res1, res2] = await promise
expect(res1).toBe('1')
expect(res2).toBe('2')
})
})
8 changes: 4 additions & 4 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ export function pseudoCancellable<T>(promise: PromiseLike<T>): CancellablePromis
/**
* The type of the `capture` function used in [[`buildCancellablePromise`]].
*/
export type CaptureCancellablePromise = <T>(
promise: CancellablePromise<T>
) => CancellablePromise<T>
export type CaptureCancellablePromise = <P extends CancellablePromise<unknown>>(
promise: P
) => P

/**
* Used to build a single [[`CancellablePromise`]] from a multi-step asynchronous
Expand All @@ -73,7 +73,7 @@ export type CaptureCancellablePromise = <T>(
* a regular `Promise`
*/
export function buildCancellablePromise<T>(
innerFunc: (capture: CaptureCancellablePromise) => Promise<T>
innerFunc: (capture: CaptureCancellablePromise) => PromiseLike<T>
): CancellablePromise<T> {
const capturedPromises: CancellablePromise<unknown>[] = []

Expand Down

0 comments on commit ee4eb6b

Please sign in to comment.