0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【TypeScript】async関数のシグネチャを転送する方法

Posted at

結論

結論
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で考える。

非async関数,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で型付けするには以下のようにすれば良い。

非async関数,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関数の場合もそのまま考えれば以下で良さそうなのだが。

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>の部分を以下のように変更する。

async関数,動く✅
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;
  };
}

AwaitedPromise<T>型からTを抜き出す型関数で、TypeScriptにバージョン4.5以降標準装備されている。よってPromise<Awaited<T>>はTがPromiseの場合Tそのものになり通常無意味な操作だが、今回は明示的にPromiseを指定するためにこうしている。こうすればエラーにならない。

TypeScript 4.5以前の場合

TypeScript 4.5以前ではAwaited<T>が標準ではないので自作する必要がある。以下のコードで代替できる。

自作Awaited
type Awaited<T> = T extends Promise<infer R> ? R : T
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?