如何以类型安全的方式在 Typescript 3.x 中表达偏函数应用?
How do I express partial function application in Typescript 3.x in a type-safe way?
我正在开发一个 Angular 代码库,它对大多数 API 调用进行一些标准的后处理。这是在服务 class 中完成的,该服务将 HttpClient.get()
等包装在通过一堆拦截方法管道 returned observable 的方法中。
令我沮丧的是,这是使用以下模式完成的:
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);
}),
);
}
这让我很烦,因为选项参数有一个相当多毛的类型,它的确切形状对于 TypeScript 的重载解析很重要,并决定了调用的 return 类型。
我正在尝试找出一种更少复制粘贴、类型安全的包装调用的方法,但我不知道如何捕获选项参数的类型。
我目前拥有的是:
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
上会给我重载HttpClient.get()
call()
是包装函数,它与原始函数进行相同的拦截,但是将 HttpClient.get()
与 URL 已经使用 Function.bind()
部分应用到可选回调.
- 此回调的作用是在调用者需要时向 HttpClient 方法提供
options
参数的值。
我迷路的地方是弄清楚什么是正确的构造来告诉 TypeScript partial
回调的参数应该是相应 HttpClient
方法的参数,except 第一个 (url
) 参数。或者让我以类型安全的方式执行此操作的其他方法,即如果我这样做,自动完成和重载解析应该可以正常工作:
this.call('get', 'https://example.com/foo/bar',
get => get({
// options for `HttpClient.get()`
})
);
Stackblitz link 上面的可运行示例:https://stackblitz.com/edit/httpclient-partial
了解 angular 的人可以充实这个或给出更有针对性的答案,但我要解决这个问题:
tell TypeScript that the parameters of the partial
callback should be the parameters of the corresponding HttpClient
method, except the first (url
) parameter.
如果您试图从函数类型中去除第一个参数,这在 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
的 return 类型和 TO
的超类型,您显然已经知道如何处理。
重要的部分是args
剩余参数,它被推断与HttpClient[M]
的参数相同,但第一个参数被剥离。这应该会给你你打电话时所期望的提示 call()
:
call('get', 'https://example.com/foo/bar',
get => get({
// hints should appear here
})
);
无论如何,希望能帮助您指明正确的方向。祝你好运!
我正在开发一个 Angular 代码库,它对大多数 API 调用进行一些标准的后处理。这是在服务 class 中完成的,该服务将 HttpClient.get()
等包装在通过一堆拦截方法管道 returned observable 的方法中。
令我沮丧的是,这是使用以下模式完成的:
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);
}),
);
}
这让我很烦,因为选项参数有一个相当多毛的类型,它的确切形状对于 TypeScript 的重载解析很重要,并决定了调用的 return 类型。
我正在尝试找出一种更少复制粘贴、类型安全的包装调用的方法,但我不知道如何捕获选项参数的类型。
我目前拥有的是:
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
上会给我重载HttpClient.get()
call()
是包装函数,它与原始函数进行相同的拦截,但是将HttpClient.get()
与 URL 已经使用Function.bind()
部分应用到可选回调.- 此回调的作用是在调用者需要时向 HttpClient 方法提供
options
参数的值。
我迷路的地方是弄清楚什么是正确的构造来告诉 TypeScript partial
回调的参数应该是相应 HttpClient
方法的参数,except 第一个 (url
) 参数。或者让我以类型安全的方式执行此操作的其他方法,即如果我这样做,自动完成和重载解析应该可以正常工作:
this.call('get', 'https://example.com/foo/bar',
get => get({
// options for `HttpClient.get()`
})
);
Stackblitz link 上面的可运行示例:https://stackblitz.com/edit/httpclient-partial
了解 angular 的人可以充实这个或给出更有针对性的答案,但我要解决这个问题:
tell TypeScript that the parameters of the
partial
callback should be the parameters of the correspondingHttpClient
method, except the first (url
) parameter.
如果您试图从函数类型中去除第一个参数,这在 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
的 return 类型和 TO
的超类型,您显然已经知道如何处理。
重要的部分是args
剩余参数,它被推断与HttpClient[M]
的参数相同,但第一个参数被剥离。这应该会给你你打电话时所期望的提示 call()
:
call('get', 'https://example.com/foo/bar',
get => get({
// hints should appear here
})
);
无论如何,希望能帮助您指明正确的方向。祝你好运!