Description
Suggestion
π Search Terms
void, async, second-order functions
β Viability Checklist
My suggestion meets these guidelines:
- This wouldn't be a breaking change in existing TypeScript/JavaScript code
- This wouldn't change the runtime behavior of existing JavaScript code
- This could be implemented without emitting different JS based on the types of the expressions
- This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
- This feature would agree with the rest of TypeScript's Design Goals.
β Suggestion (and motivating example)
(I apologize if there is already discussion around this; I haven't been able to find it.)
It is sometimes convenient for a second-order function to receive a function that may or may not be asynchronous:
function mySecondOrderFn(firstOrderFn: () => void | Promise<void>) {
return async () => {
console.log("Calling firstOrderFn()")
const output = await firstOrderFn()
console.log("Finished calling firstOrderFn()")
}
}
// @ts-expect-error I'd like to be able to do this:
mySecondOrderFn(() => 2)
// @ts-expect-error Or this, for that matter:
mySecondOrderFn(async () => 2)
Unfortunately () => void | Promise<void>
seems to cancel TS's normal type inference around () => void
:
const x: () => void = () => 2
// @ts-expect-error Type 'number' is not assignable to type 'void | Promise<void>
const y: () => void | Promise<void> = () => 2
In addition (this is probably the root cause): () => Promise<void>
by itself is pickier than I would expect since only () => Promise<undefined>
and () => Promise<any>
are assignable to it. I understand that void
is not an inferrable type but this case does not require void
to be inferred, does it?
I suspect this problem exists because of how void
interacts with parameterized types. Maybe there isn't a general solution to that, but at least I think () => Promise<2> extends () => Promise<void>
would be desirable to support as a special case, given the ubiquity of the async/await
pattern.
π» Use Cases
In my case, I'm writing an implementation of forEach. It is a context that applies backpressure so I want the first-order function to be able to return a Promise, but otherwise I don't care about the output.
() => any
does suffice here; () => any | Promise<any>
is marginally more expressive, although from a type perspective it is exactly the same. In either case I am exposed to the pitfalls of the any
type, in particular that is easy to assign such output to a value by mistake (precisely what void
exists to prevent you from doing).