承诺拒绝类型

我如何设置拒绝承诺的类型? 让我们假设我这样做了:

const start = (): Promise<string> => {
return new Promise((resolve, reject) => {
if (someCondition) {
resolve('correct!');
} else {
reject(-1);
}
});
}

假设我想用一个数字拒绝。但是我不能设置类型; 我可以在这里向 reject传递任何我想传递的内容。

此外,在使用此承诺时,如果不正确地使用拒绝响应类型,就会出现编译错误。

59928 次浏览

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 throwing 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.

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.

source

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)
}
})

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;
}
}
});

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.