You can use a proxy to explicitly force resolve
and reject
argument types. The following example does not try to mimic the constructor of a promise - because I didn't find that useful in practice. I actually wanted to be able to call .resolve(...)
and .reject(...)
as functions outside of the constructor. On the receiving side the naked promise is used - e.g., await p.promise.then(...).catch(...)
.
export type Promolve<ResT=void,RejT=Error> = {
promise: Promise<ResT>;
resolve: (value:ResT|PromiseLike<ResT>) => void;
reject:(value:RejT) =>void
};
export function makePromolve<ResT=void,RejT=Error>(): Promolve<ResT,RejT> {
let resolve: (value:ResT| PromiseLike<ResT>)=>void = (value:ResT| PromiseLike<ResT>)=>{}
let reject: (value:RejT)=>void = (value:RejT)=>{}
const promise = new Promise<ResT>((res,rej) => {
resolve = res;
reject = rej;
});
return { promise, resolve, reject };
}
The let
statements look as if they are pointless - and they are pointless at runtime. But it stops compiler errors that were not easy to resolve otherwise.
(async()=>{
const p = makePromolve<number>();
//p.resolve("0") // compiler error
p.resolve(0);
// p.reject(1) // compiler error
p.reject(new Error('oops'));
// no attempt made to type the receiving end
// just use the named promise
const r = await p.promise.catch(e=>e);
})()
As shown, calls to .resolve
and .reject
are properly typed checked.
No attempt is made in the above to force type checking on the receiving side.
I did poke around with that idea, adding on .then
and .catch
members, but then what should they return? If they return a Promise
then it goes back to being a normal promise, so it is pointless. And it seems there is no choice but to do that. So the naked promise is used for await
, .then
and .catch
.