Typescript Promise rejection type
JavascriptTypescriptPromiseJavascript Problem Overview
How do I set the type of the rejection of my promise? Let's say I do:
const start = (): Promise<string> => {
return new Promise((resolve, reject) => {
if (someCondition) {
resolve('correct!');
} else {
reject(-1);
}
});
}
Let's say I want to reject with a number. But I cannot set the type; I can pass whatever I want to the reject
here.
Moreover, when using this promise, I want to have compiling error if I use the rejection response type incorrectly.
Javascript Solutions
Solution 1 - Javascript
As explained in this issue, Promise
doesn't have different types for fulfilled and rejected promises. reject
accepts any
argument that doesn't affect type of a promise.
Currently Promise
cannot be typed any better. This results from the fact that a promise can be rejected by throw
ing inside then
or catch
(this is a preferable way to reject existing promise), and this cannot be handled by typing system; also, TypeScript also doesn't have exception-specific types except never
.
Solution 2 - Javascript
Cause there is no way to set error type in some cases like Promise, or exception throws, we can work with errors in rust-like style:
// Result<T, E> is the type used for returning and propagating errors.
// It is an sum type with the variants,
// Ok<T>, representing success and containing a value, and
// Err<E>, representing error and containing an error value.
export type Ok<T> = { _tag: "Ok"; ok: T };
export type Err<E> = { _tag: "Err"; err: E };
export type Result<T, E> = Ok<T> | Err<E>;
export const Result = Object.freeze({
Ok: <T, E>(ok: T): Result<T, E> => ({ _tag: "Ok", ok }),
Err: <T, E>(err: E): Result<T, E> => ({ _tag: "Err", err }),
});
const start = (): Promise<Result<string, number>> => {
return new Promise((resolve) => {
resolve(someCondition ? Result.Ok("correct!") : Result.Err(-1));
});
};
start().then((r) => {
switch (r._tag) {
case "Ok": {
console.log(`Ok { ${r.ok} }`);
break;
}
case "Err": {
console.log(`Err { ${r.err} }`);
break;
}
}
});
Solution 3 - Javascript
> The exception is typed any because we cannot guarantee the correct > type of the exception at design time, and neither TypeScript nor > JavaScript provide the ability to guard exception types at run time. Your best option is to use type guards to provide both a design-time and run-time check in your code.
Solution 4 - Javascript
What @EstusFlask mentioned in his answer is correct.
> But I want go one step near to an artificial solution to
> simulate what we want with TypeScript
capabilities.
Sometimes I use this pattern in my codes:
interface IMyEx{
errorId:number;
}
class MyEx implements IMyEx{
errorId:number;
constructor(errorId:number) {
this.errorId = errorId;
}
}
// -------------------------------------------------------
var prom = new Promise(function(resolve, reject) {
try {
if(..........)
resolve('Huuuraaa');
else
reject(new MyEx(100));
}
catch (error) {
reject(new MyEx(101));
}
});
// -------------------------------------------------------
prom()
.then(success => {
try {
}
catch (error) {
throw new MyEx(102);
}
})
.catch(reason=>{
const myEx = reason as IMyEx;
if (myEx && myEx.errorId) {
console.log('known error', myEx)
}else{
console.log('unknown error', reason)
}
})
Solution 5 - Javascript
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
.