One way to ensure the argument types of resolve
and reject
in JavaScript is to use a proxy. In the example below, there is no attempt to simulate the promise constructor, as the focus is on enabling calling .resolve()
and .reject()
as standalone functions. On the consuming end, the naked promise is utilized with syntax like
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 };
}
While the let
statements may seem redundant at runtime, they serve the purpose of preventing compiler errors that are otherwise difficult to resolve.
(async() => {
const p = makePromolve<number>();
//p.resolve("0") // compiler error
p.resolve(0);
// p.reject(1) // compiler error
p.reject(new Error('oops'));
// simply use the named promise without enforcing types on the receiving end
const r = await p.promise.catch(e=>e);
})()
By following this approach, calls to .resolve
and .reject
can be dynamically checked for correct types.
In contrast, no effort is made here to impose type checking on the receiving side. While exploring this idea, adding .then
and .catch
members raised questions about returning values. Should they return a Promise
, it would revert back to a standard promise operation. As a result, sticking to using the naked promise for operations like await
, .then
, and .catch
seems most practical.