Как я могу выразить частичное применение функции в Typescript 3.x безопасным для типов способом?
Я работаю над кодовой базой 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
дает мне перегрузки дляHttpClient.get()
call()
является функцией-оберткой, которая выполняет тот же перехват, что и оригинал, но передаетHttpClient.get()
с уже частично примененным URL с помощьюFunction.bind()
на дополнительный обратный вызов.- Роль этого обратного вызова заключается в предоставлении значения
options
параметр to из методов HttpClient, если вызывающая сторона хочет.
Я теряюсь в том, чтобы выяснить, какова правильная конструкция, чтобы сказать TypeScript, что параметры partial
обратным вызовом должны быть параметры соответствующего HttpClient
метод, кроме первого (url
) параметр. Или какой-то альтернативный способ, позволяющий мне сделать это безопасным для типов способом, то есть автозаполнение и разрешение перегрузки должны работать правильно, если я это сделаю:
this.call('get', 'https://example.com/foo/bar',
get => get({
// options for `HttpClient.get()`
})
);
Ссылка Stackblitz для работающего примера выше: https://stackblitz.com/edit/httpclient-partial
1 ответ
Кто-то, обладающий знанием углов, может конкретизировать это или дать более целенаправленный ответ, но я собираюсь ответить на этот вопрос:
скажите 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
})
);
В любом случае, надеюсь, это поможет вам в правильном направлении. Удачи!