Patterns
How do I accept callbacks that return either `T` or `Promise<T>` and always expose one predictable result?
Accept a maybe-promise, await it, and describe the flattened result with `Awaited`.
The recipe
type MaybePromise<T> = T | Promise<T> async function run<T>( task: () => MaybePromise<T>, ): Promise<Awaited<T>> { return (await task()) as Awaited<T> } const syncResult = run(() => 42) const asyncResult = run(async () => 'ready')
The build compiles this and checks each result below.
How it works
- 01
type MaybePromise<T> = T | Promise<T>The callback contract permits immediate and deferred implementations.
- 02
Promise<Awaited<T>>
The public result is always asynchronous and nested promises are flattened.
What you get
typeof syncResult→Promise<number>typeof asyncResult→Promise<string>Awaited<typeof asyncResult>→string
Where it goes wrong
The returned API is always asynchronous, even for a synchronous callback. Do not use this wrapper where callers rely on immediate exceptions or same-tick completion.
Takeaway
Normalize flexible inputs at the boundary so every caller consumes one stable output shape.
