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?

【TypeScript】() => Promise<Response>とPromise<Response>——引数に渡すときの違い

0
Posted at

はじめに

この投稿は、TypeScript学習者が書いています。関数の引数として() => Promise<Response>を受け取る書き方と、Promise<Response>を受け取る書き方の違いを、自身の理解のために整理します。

例として、fetchtry/catchで包む関数を書く場面を考えます。引数の型がどう違うのか、いつ処理が始まるのかを見ていきます。

fetch("/example")をそのまま渡す場合と、アロー関数で渡す場合は何が違うのか、型エラーの理由、引数をPromise<Response>にした場合、実行タイミングの違いを比較します。

1. () => Promise<Response>Promise<Response>

同じfetch("/example")を扱うとき、次の2つは別物です。

const run: () => Promise<Response> = () => fetch("/example");
const promise: Promise<Response> = fetch("/example");
書き方 いつ処理が始まるか
() => fetch(url) () => Promise<Response> run()を呼んだとき
fetch(url) Promise<Response> その行を評価したとき

run: () => Promise<Response>は関数の型で、あとから実行する関数を受け取ります。fetch(url)を引数に直接書くと、引数を評価した時点でfetchが実行されます。

2. () => Promise<Response>を受け取るとき

次のcatchNetworkErrorは、fetchtry/catchで包む関数です。tryの中でawait run()し、必要なのはPromise<Response>そのものではなく、呼ぶとPromise<Response>が返る関数です。

declare function fetch(input: string): Promise<Response>;

async function catchNetworkError(
  run: () => Promise<Response>,
): Promise<Response> {
  try {
    return await run();
  } catch {
    throw new Error("失敗しました");
  }
}

catchNetworkError(() => fetch("/example")); // OK
catchNetworkError(fetch("/example")); // 型エラー
// Argument of type 'Promise<Response>' is not assignable to parameter of type '() => Promise<Response>'.
//  Type 'Promise<Response>' provides no match for the signature '(): Promise<Response>'.

fetch("/example")の型はPromise<Response>です。() => Promise<Response>には代入できません。アロー関数で渡すと、() => fetch("/example")全体が関数の型になり、run()の時点でfetchが実行されます。

3. Promise<Response>を受け取るとき

引数の型をPromise<Response>に変えれば、fetch(url)をそのまま渡せます。

async function catchNetworkError(
  promise: Promise<Response>,
): Promise<Response> {
  try {
    return await promise;
  } catch {
    throw new Error("失敗しました");
  }
}

catchNetworkError(fetch("/example"));

fetch("/example")は呼び出しの前に引数の式として評価され、その時点で返ったPromisecatchNetworkErrorに入ります。catchNetworkErrorの中ではtryawait promiseしているので、失敗してrejectされてもcatchで拾えます。型の違いは、いつfetchが実行されるかです。

実行順の違い

Promiseを渡すと、ラッパーに入る前にfetchが走ります。アロー関数を渡すと、ラッパーの中でrun()が呼ばれたときにfetchが走ります。

const log: string[] = [];

function trackFetch(): Promise<Response> {
  log.push("fetch");
  return Promise.resolve({} as Response);
}

async function wrapPromise(promise: Promise<Response>) {
  log.push("enter");
  return promise;
}

async function wrapRun(run: () => Promise<Response>) {
  log.push("enter");
  return run();
}

async function demoOrder() {
  log.length = 0;
  await wrapPromise(trackFetch());
  console.log(log); // ["fetch", "enter"]

  log.length = 0;
  await wrapRun(() => trackFetch());
  console.log(log); // ["enter", "fetch"]
}

demoOrder();

同じ処理を何度も実行したいとき

リトライのように、同じfetchを何度も実行したい場面では、() => Promise<Response>で渡す必要があります。Promiseは1回だけ、run()は呼ぶたびに実行されます。

let count = 0;

function trackFetch(): Promise<Response> {
  count += 1;
  console.log("fetch", count);
  return Promise.resolve({} as Response);
}

async function demoCount() {
  count = 0;
  const promise = trackFetch();
  await promise;
  await promise;
  console.log("promise版", count); // fetch 1 → promise版 1

  count = 0;
  const run = () => trackFetch();
  await run();
  await run();
  console.log("run版", count);
  // fetch 1
  // fetch 2
  // run版 2
}

demoCount();

まとめ

  • 引数の型が() => Promise<Response>のとき、fetch(...)そのもの(型はPromise<Response>)はそのまま渡せません。呼ぶとfetchが始まる関数を渡します。アロー関数でも関数宣言でも構いません。
  • 引数をPromise<Response>にすればfetch(...)を直接渡せます。tryawaitすればrejectも拾えます。
  • 引数の評価時ではなく、受け取る関数の中で実行したいときや、同じ処理を何度も実行したいときは、() => Promise<Response>を使います。
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?