如何设置拒绝承诺的类型?假设我愿意:
const start = (): Promise<string> => {
return new Promise((resolve, reject) => {
if (someCondition) {
resolve('correct!');
} else {
reject(-1);
}
});
}
假设我想用号码拒绝。但我无法设置类型;我可以将我想要的任何东西传递给这里的
reject
。
此外,当使用这个承诺时,如果我错误地使用拒绝响应类型,我希望出现编译错误。
如本期中所述,
Promise
对于已履行和已拒绝的承诺没有不同的类型。 reject
接受不影响承诺类型的any
参数。
目前
Promise
无法更好地打字。这是因为 Promise 可以通过在 throw
或 then
内部进行 catch
来拒绝(这是拒绝现有 Promise 的更好方法),而这不能通过打字系统来处理;此外,除了 never
之外,TypeScript 也没有特定于异常的类型。
因为在某些情况下无法设置错误类型,例如 Promise 或异常抛出,我们可以像 Rust 一样处理错误:
// 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;
}
}
});
异常类型为any,因为我们不能保证正确 设计时异常的类型,TypeScript 和 JavaScript 提供了在运行时保护异常类型的能力。 最好的选择是使用类型防护在代码中提供设计时和运行时检查。
这是我尝试输入的内容:
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>;
}
像平常一样使用它:
return new ErrPromise<T,ExecError>((resolve, reject) => { ... })
您的 IDE 应选择
reject
的类型:
@EstusFlask 在他的回答中提到的内容是正确的。
但是我想进一步接近人工解决方案 使用
TypeScript
功能模拟我们想要的东西。
有时我在代码中使用这种模式😉:
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)
}
})
您可以使用代理显式强制
resolve
和 reject
参数类型。以下示例不会尝试模仿 Promise 的构造函数 - 因为我发现这在实践中没有用。我实际上希望能够在构造函数之外调用 .resolve(...)
和 .reject(...)
作为函数。在接收方,使用赤裸裸的承诺 - 例如,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 };
}
let
语句看起来好像毫无意义——而且它们在运行时也是毫无意义的。但它可以阻止编译器错误,否则这些错误很难解决。
(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);
})()
如图所示,对
.resolve
和 .reject
的调用已正确键入检查。
上面没有尝试强制接收方进行类型检查。 我确实考虑过这个想法,添加了
.then
和 .catch
成员,但是他们应该返回什么?如果他们返回 Promise
那么它又回到正常的承诺,所以这是毫无意义的。似乎别无选择,只能这样做。因此,赤裸裸的承诺用于 await
、.then
和 .catch
。
这是我的解决方案:
定义一个扩展原始
PromiseConstructor
和 Promise
的类型:
interface TypedPromiseConstructor<ResolveType, RejectType> extends PromiseConstructor {
/**
* Creates a new Promise.
* @param executor A callback used to initialize the promise. This callback is passed two arguments:
* a resolve callback used to resolve the promise with a value or the result of another promise,
* and a reject callback used to reject the promise with a provided reason or error.
*/
new <T = ResolveType, R = RejectType>(
executor: (resolve: (value: T | PromiseLike<T>) => void, reject: (reason?: R) => void) => void
): TypedPromise<T, R>;
}
interface TypedPromise<ResolveType, RejectType> extends Promise<ResolveType> {
/**
* Attaches a callback for only the rejection of the Promise.
* @param onrejected The callback to execute when the Promise is rejected.
* @returns A Promise for the completion of the callback.
*/
catch<TResult = never>(
onrejected?: ((reason: RejectType) => TResult | PromiseLike<TResult>) | undefined | null
): TypedPromise<ResolveType | TResult>;
/**
* Attaches callbacks for the resolution and/or rejection of the Promise.
* @param onfulfilled The callback to execute when the Promise is resolved.
* @param onrejected The callback to execute when the Promise is rejected.
* @returns A Promise for the completion of which ever callback is executed.
*/
then<TResult1 = ResolveType, TResult2 = never>(
onfulfilled?: ((value: ResolveType) => TResult1 | PromiseLike<TResult1>) | undefined | null,
onrejected?: ((reason: RejectType) => TResult2 | PromiseLike<TResult2>) | undefined | null
): Promise<TResult1 | TResult2>;
}
然后在使用时将
Promise
类型转换为 TypedPromiseConstructor
:
const promise = new (Promise as TypedPromiseConstructor<number, Error>)((resolve, reject) => {
if (Date.now() % 2 === 0) {
reject(new Error(`Worker stopped with exit code: 1`));
} else {
resolve(0);
}
});
这是我解决它的方法(类似于@moontai0724):
// typed-promise.d.ts
interface TypedPromise<RESOLVE, REJECT> extends Promise<RESOLVE> {
then<TResult1 = RESOLVE, TResult2 = never>(
onfulfilled?: (((value: RESOLVE) => (TResult1 | PromiseLike<TResult1>)) | undefined | null),
onrejected?: (((reason: (REJECT | any)) => (TResult2 | PromiseLike<TResult2>)) | undefined | null)
): TypedPromise<TResult1 | TResult2>;
catch<TResult = never>(onrejected?: (((reason: (REJECT | any)) => (TResult | PromiseLike<TResult>)) | undefined | null)): TypedPromise<RESOLVE | TResult>;
finally(onfinally?: ((() => void) | undefined | null)): TypedPromise<RESOLVE | REJECT>;
}
// typed-promise-constructor.d.ts
interface TypedPromiseConstructor<RESOLVE, REJECT> extends PromiseConstructor {
new<RES = RESOLVE, REJ = REJECT>(
executor: ((resolve: ((value: (RES | PromiseLike<RES>)) => void), reject: ((reason?: REJ) => void)) => void)
): TypedPromise<RES, REJ>;
}
declare var TypedPromise: TypedPromiseConstructor;