結論
function wrapFn<T extends (...args: any[]) => Promise<any>>(fn: T) {
return async (...args: Parameters<T>): Promise<Awaited<ReturnType<T>>> => {
return await fn(...args);
};
}
解説
TypeScript 4.7.4で試した。
関数のシグネチャを転送するとは
関数を引数にとって同じパラメータおよび戻り値の関数を返す関数を書きたい時がある。例えば関数の呼び出しのログをとる関数をJavaScriptで考える。
function wrapLogger(fn) {
return (...args) => {
const ret = fn(...args);
console.log('function called with', ...args, 'and returns', ret);
return ret;
};
}
// 使い方
const loggedPlus = wrapLogger((a, b) => a + b);
const result = loggedPlus(3, 4); // コンソール出力: function called with 3 4 and returns 7
これをTypeScriptで型付けするには以下のようにすれば良い。
function wrapLogger<T extends (...args: any[]) => any>(fn: T) {
return (...args: Parameters<T>): ReturnType<T> => {
const ret = fn(...args);
console.log('function called with', ...args, 'and returns', ret);
return ret;
};
}
// 使い方
const loggedPlus = wrapLogger((a: number, b: number) => a + b);
const result = loggedPlus(3, 4); // コンソール出力: function called with 3 4 and returns 7
上のようにParameters<T>
とReturnType<T>
を使うことで引数の関数のシグネチャを戻り値の関数のシグネチャに転送できる。この場合、loggedPlus
の型は(a: number, b: number) => number
型としてきちんと認識される。
async関数の場合
上記はasyncではない普通の関数の場合であった。async関数の場合もそのまま考えれば以下で良さそうなのだが。
// これはエラーになる
function wrapLogger<T extends (...args: any[]) => Promise<any>>(fn: T) {
return async (...args: Parameters<T>): ReturnType<T> => {
const ret = await fn(...args);
console.log('function called with:', ...args, 'and returns', ret);
return ret;
};
}
// 使い方
(async () => {
const loggedPlus = wrapLogger(async (a: number, b: number) => a + b);
const result = await loggedPlus(3, 4);
})();
これは3行目のReturnType<T>
でエラーになる。
The return type of an async function or method must be the global Promise type. Did you mean to write 'Promise<any>'?
どうやらasync関数の戻り値型には明示的にPromise
型を指定しないとエラーになるらしい。ReturnType<T>
は型制約でPromise<any>
であることがわかっているので通してくれても良さそうだけどね。
ということで明示的にPromise
を使うためにReturnType<T>
の部分を以下のように変更する。
function wrapLogger<T extends (...args: any[]) => Promise<any>>(fn: T) {
return async (...args: Parameters<T>): Promise<Awaited<ReturnType<T>>> => {
const ret = await fn(...args);
console.log('function called with:', ...args, 'and returns', ret);
return ret;
};
}
Awaited
はPromise<T>
型からT
を抜き出す型関数で、TypeScriptにバージョン4.5以降標準装備されている。よってPromise<Awaited<T>>
はTがPromise
の場合T
そのものになり通常無意味な操作だが、今回は明示的にPromise
を指定するためにこうしている。こうすればエラーにならない。
TypeScript 4.5以前の場合
TypeScript 4.5以前ではAwaited<T>
が標準ではないので自作する必要がある。以下のコードで代替できる。
type Awaited<T> = T extends Promise<infer R> ? R : T