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.
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
.
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;
}
}
});
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.
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)
}
})
You can now use PromiseRejectionEvent
to type this (in everything except IE, of course):
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 - eg, 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
.
Here's my attempt at typing it:
export class ErrPromise<TSuccess, TError> extends Promise<TSuccess> {
constructor(executor: (resolve: (value: TSuccess | PromiseLike<TSuccess>) => void, reject: (reason: TError) => void) => void) {
super(executor);
// Object.setPrototypeOf(this, new.target.prototype); // restore prototype chain
}
}
export interface ErrPromise<TSuccess, TError = unknown> {
then<TResult1 = TSuccess, TResult2 = never>(onfulfilled?: ((value: TSuccess) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: TError) => TResult2 | PromiseLike<TResult2>) | undefined | null): Promise<TResult1 | TResult2>;
catch<TResult = never>(onrejected?: ((reason: TError) => TResult | PromiseLike<TResult>) | undefined | null): Promise<TSuccess | TResult>;
}
Use it like normal:
return new ErrPromise<T,ExecError>((resolve, reject) => { ... })
Your IDE should pick up the type of reject
:
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.