如何以类型安全的方式在Typescript 3.x中表达部分函数应用程序?

时间:2018-12-04 17:31:02

标签: angular typescript angular-httpclient partial-application

我正在使用Angular代码库,该代码库对大多数API调用进行一些标准的后处理。这是在服务类中完成的,该服务类将HttpClient.get()等包装在通过一堆拦截方法通过管道传递返回的可观察对象的方法中。

令我沮丧的是,使用以下模式完成了此操作:

public get(url, options?) {
  const method = 'GET';
  return this._http.get(url, options).pipe(
    map((resp: any) => {
      console.log(`Calling ${method} ${url} returned`, resp);
      return resp;
    }),
    catchError(err => {
      console.error(`Calling ${method} ${url} failed`, err);
      throw(err);
    }),
  );
}

这让我很烦,因为options参数具有相当多的类型,其确切形状对于TypeScript的重载分辨率很重要,并确定了调用的返回类型。

我试图找出一种不太复制粘贴,类型安全的方式包装调用,但是我不知道如何捕获options参数的类型。

到目前为止,我的是:

export class HelloComponent {
  @Input() name: string;
  response: any;

  constructor(private _http: HttpClient) {
    const httpClientGet = this.method('get');

    const response = this.call('get', 'https://example.com/foo/bar');

    response.subscribe(
      data => this.response = JSON.stringify(data, null, 2),
      (err: HttpErrorResponse) => this.response = err.error
    );

  }

  call<T>(
    method: keyof HttpClient,
    url: string,
    handler: <TObs extends Observable<T>>(partial: (options?: any) => TObs) => TObs = (_ => _())) /* HOW DO I GET THE CORRECT TYPE OF OPTIONS HERE? */
    : Observable<T> {

    const u = new URL(url);
    console.info(`Calling ${method.toUpperCase()} ${u.pathname}`);

    const result = handler(this._http[method].bind(this._http, url)).pipe(
      map((resp) => {
        console.log(`Calling ${method.toUpperCase()} ${u.pathname} returned`, resp);
        return resp;
      }),
      catchError(err => {
        console.error(`Calling ${method.toUpperCase()} ${u.pathname} failed`, err);
        throw err;
      })
    )

    console.info('Returning', result);
    return result;
  }

  method<TMethod extends keyof HttpClient>(name: TMethod): HttpClient[TMethod] {
    return this._http[name];
  }
}

也就是说:

  • 我知道我可以通过将其名称正确地作为字符串文字传递给方法来捕获正在调用的HttpClient方法的签名,将鼠标悬停在httpClientGet上可以为{{ 1}}
  • HttpClient.get()是一种包装函数,其功能与原始功能相同,但是将call()的URL已使用HttpClient.get()部分地应用到可选回调中。
  • 如果调用者愿意,此回调的作用是从HttpClient方法提供Function.bind()参数的值。

我迷失的地方是找出正确的结构来告诉TypeScript options回调的参数应该是相应的partial方法的参数,除外第一个(HttpClient)参数。或让我以类型安全的方式执行此操作的其他替代方法,即自动填充和重载解析应在我正确执行的情况下进行:

url

上述可运行示例的Stackblitz链接:https://stackblitz.com/edit/httpclient-partial

1 个答案:

答案 0 :(得分:2)

了解角度的​​人可以充实或给出更有针对性的答案,但是我要解决这个问题:

  

告诉TypeScript partial回调的参数应该是相应的HttpClient方法的参数,但第一个(url)参数除外。

如果您尝试从函数类型中剥离第一个参数,则可以在TypeScript 3.0及更高版本中实现:

type StripFirstParam<F> = 
  F extends (first: any, ...rest: infer A)=>infer R ? (...args: A)=>R : never

因此,对于您的call()方法,我可以想象它看起来像 一样:

declare function call<M extends keyof HttpClient>(
  method: M, 
  url: string, 
  handler: <TO>(
    partial: (
      ...args: (HttpClient[M] extends (x: any, ...rest: infer A) => any ? A : never)
    ) => TO
  ) => TO
): void;

我故意遗漏了call的返回类型和TO的超类型,您显然已经知道该如何处理。

重要的部分是args的rest参数,可以推断为与HttpClient[M]的参数相同,但第一个参数被删除。那应该给您打电话时的提示,call()

call('get', 'https://example.com/foo/bar',
  get => get({
    // hints should appear here
  })
);

无论如何,希望能帮助您指出正确的方向。祝你好运!