typescript 如何承诺对象中的所有回调样式方法

vfh0ocws  于 2023-02-13  发布在  TypeScript
关注(0)|答案(1)|浏览(133)

我对typescript比较陌生。我想创建一个通用的 Package 器/实用程序,它将接受一个带有回调风格方法(事先未知)的对象,并承诺它们。具体的用例是承诺自动生成的节点grpc客户端。
使用回调样式方法的客户端的示例类型:

type AutogeneratedClient = {
    autogeneratedMethod(request: MethodSpecificRequestType, callback: (error: ServiceError, response: MethodSpecificResponseType) => void): ClientUnaryCall
}

我希望能够有一个通用的“promisify”实用程序,它将返回一个对象,如以下类型所描述的:

type PromisifiedAutogeneratedClient = {
    autogeneratedMethod(request: MethodSpecificRequestType): Promise<MethodSpecificResponseType>
}

我想把它实现为一个代理,使用“get”陷阱返回一个 Package 器函数,该函数承诺原始的基于回调的方法。问题是,我不知道如何使用Typescript来实现它。我开始研究Mapped类型,但我仍然无法使其工作。
你将如何以类型安全的方式解决这个问题?

cnwbcb6i

cnwbcb6i1#

我的想法沿着如下:
首先,让我们定义一些类型,我不知道gRPC是如何一般性地定义它的客户机的,所以我将假设类似于上面所示的类型:

type GrpcClientFn<REQ, RES> =
  (req: REQ, cb: (e: ServiceError, res: RES)  => void) => ClientUnaryCall;

这自然会把我们引向相应的承诺类型:

type PromisifiedGrpcClientFn<REQ, RES> = (req: REQ) => Promise<RES>;

现在的类型为 * 一个单一的客户端函数“promisifier”*,不完全是你想要的,但它的垫脚石,并实现:

type Promisify<REQ, RES, F extends Function> =
  F extends GrpcClientFn<REQ, RES> ? PromisifiedGrpcClientFn<REQ, RES> : never;

function promisify<REQ, RES, FIN extends GrpcClientFn<REQ,RES>>(fin: FIN): PromisifiedGrpcClientFn<REQ, RES> {
  return function(req: REQ) {
    return new Promise((resolve, reject) => {
      fin(req, (error, outcome) => {
        if (error) {
          reject(error);
        } else {
          resolve(outcome);
        }
      });
    });
  }
}

它接受一个gRPC风格的函数并对其进行承诺(在风格上,我在某些地方选择使用旧式的function(a,b,c) { ... }语法而不是现代的(a,b,c) => { ... }语法,以使返回类型显式)。
好了,困难的事情过去了:现在定义一个值为gRPC客户端函数的整个对象:

type GrpcClientObj = {
  [key: string]: GrpcClientFn<any, any>;
}

我希望我能做一些更好的<any,any>,但我想不出什么!
在定义“promisized”对象类型之前,我需要两个助手来提取请求和响应参数类型:

// obtain type of request parameter
type PREQ<F extends Function> =
  F extends (req: infer REQ, cb: (e: ServiceError, res: any)  => void) => ClientUnaryCall ? REQ : never;
// obtain type of response parameter
type PRES<F extends Function> =
  F extends (req: any, cb: (e: ServiceError, res: infer RES)  => void) => ClientUnaryCall ? RES : never;

最后,“承诺”* 对象 * 的类型为:

type PromisifiedGrpcClientObj<T extends GrpcClientObj> = {
  [P in keyof T]: Promisify<PREQ<T[P]>, PRES<T[P]>, T[P]>;
}

实施起来再简单不过了:

function promisifyObj(o: GrpcClientObj): PromisifiedGrpcClientObj<GrpcClientObj> {
  return Object.keys(o).reduce((aggr, cur) => ({
    ...aggr,
    [cur]: promisify(o[cur])
  }), {} as PromisifiedGrpcClientObj<GrpcClientObj>);
}

还有一个 typescript Playground链接。
编辑:也许,下面的描述更合适--区别在于我们如何定义输出类型与输入类型的关系:

function promisifyObj<GRPCO extends GrpcClientObj>(o: GRPCO): PromisifiedGrpcClientObj<GRPCO> {
  return Object.keys(o).reduce((aggr, cur) => ({
    ...aggr,
    [cur]: promisify(o[cur])
  }), {} as PromisifiedGrpcClientObj<GRPCO>);
}

相关问题