TypeScript 非同期処理 Promise と Fetch

Promise や async 関数(async/await)で型注釈する方法や TypeScript を使用して fetch を利用する方法などについての解説のような覚書です。

作成日:2023年12月09日

関連ページ

参考サイト

JavaScript 参考サイト

JavaScript の Promise(概要)

JavaScript の Promise は非同期処理を扱うための Promise オブジェクトとその仕組みのことです。Promise を使うことで非同期処理をその仕様に従って統一された書き方で扱うことができます。

以下は JavaScript の Promise の概要です。

コンストラクタ

Promise は new と コンストラクタ関数 Promise() で promise オブジェクトのインスタンスを作成して利用することができます。

コンストラクタ Promise() は引数に resolve と reject という関数を引数に持つ関数(executor)を受け取ります。引数の名前は通常 resolve と reject が使われますが、任意の名前を使うことができます。

const promise = new Promise(executor);

function executor(resolve, reject) {
  // 何らかの非同期処理
  // 処理が終わったら、引数に用意された resolve または reject という関数を呼び出す
}

通常、executor は以下のように無名関数としてコンストラクタの引数に指定します。

const promise = new Promise((resolve, reject) => {
  // 何らかの非同期処理
  // 処理が終わったら、引数に用意された resolve または reject という関数を呼び出す
});

コンストラクタの引数に指定する関数(executor)では何らかの処理を行い、処理が成功した場合は resolve() を呼び出し, 失敗した場合は reject() を呼び出します。

const promise = new Promise((resolve, reject) => {
  // 何らかの非同期処理

  /* 成功した場合 */
  //結果の値(value)を引数に指定して第1引数の resolve を呼び出す
  resolve(value);

  /* 失敗した場合 */
  //エラーオブジェクト(または任意の値)を引数に指定して第2引数の reject を呼び出す
  reject(Error オブジェクト);

});

非同期処理が成功した場合のみを扱う場合は resolve() のみを呼び出し、非同期処理が失敗した場合のみを扱う場合は reject() のみを呼び出すこともできます。

var promise = new Promise((resolve) => {
  // 何らかの非同期処理

  /* 成功した場合のみを扱う */
  //結果の値を引数に指定して第1引数の resolve を呼び出す
  resolve(value);

});

成功した場合のみを扱う場合は、executor の第2引数を省略できますが、失敗した場合のみを扱う場合は第1引数(以下の場合は resolve)は省略できません。

var promise = new Promise((resolve, reject) => {
  // 何らかの非同期処理

  /* 失敗した場合のみを扱う */
  //エラーオブジェクトを引数に指定して第2引数の reject を呼び出す
  reject(Error オブジェクト);

});

then() メソッド

new で生成された promise オブジェクトには、処理が成功及び失敗したそれぞれの場合に実行するコールバック関数を登録するために promise.then() というインスタンスメソッドが用意されています。

promise.then(onFulfilled, onRejected);

処理が成功した時は第1引数の onFulfilled() が呼ばれ、resolve(value) の引数 value が渡されます。

処理が失敗した時は第2引数の onRejected() が呼ばれ、reject(Error) の引数 Error が渡されます。

Promise の状態

new Promise() でインスタンス化した promise オブジェクトには以下の3つの状態が存在します。

  • pending :初期状態(fulfilled でも rejected でもない状態)
  • fulfilled :非同期処理が成功した状態(then メソッドの onFulfilled コールバックが呼ばれる)
  • rejected :非同期処理が失敗した状態(then メソッドの onRejected コールバックが呼ばれる)

pending 状態の promise オブジェクトは非同期処理の結果により、fulfilled または rejected 状態のいずれかに変わり、then メソッド によって関連付けられたコールバック関数が呼ばれ、対応する Promise の結果が渡されます。

初期状態 pending 非同期処理 処理結果 fulfilled 成功 resolve(value) が呼び出される then() の onFulfilled が呼ばれ value を受け取る rejected 失敗 reject(error) が呼び出される then() のonRejected が呼ばれ error を受け取る

Promise の結果

Promise が生成されて返された時点では、Promise はまだ結果が決まっていない(非同期処理が完了していない)ため、pending 状態で Promise の結果はこの時点では undefined になっています。

その後、非同期処理が完了した時点で、非同期処理の結果が対応する Promise インスタンスに渡され、それが Promise の結果になります。

非同期処理が成功した場合は resolve(value) に渡される value が Promise の結果になり、失敗した場合は reject(error) に渡される error が Promise の結果になります。

以下のコードを実行すると、pending 状態、fullfilled 状態、rejected 状態の promise のインスタンスが生成され、コンソールにはそれぞれの PromiseState(Promise の状態)と PromiseResult(Promise の結果)が出力されます。

const pendingPromise = new Promise((resolve, reject) => {
  // resolve() も reject() を呼び出していないので pending 状態
});
console.log(pendingPromise); // Promise {<pending>}

const fulfilledPromise = new Promise((resolve, reject) => {
  resolve('結果の値'); // resolve() を呼び出しているので fullfilled 状態
});
console.log(fulfilledPromise); // Promise {<fulfilled>: '結果の値'}

const rejectedPromise = new Promise((resolve, reject) => {
  reject(new Error('エラー')); // reject() を呼び出しているので rejected 状態
});
console.log(rejectedPromise); // Promise {<rejected>: Error: エラー}

以下は Promise オブジェクトを返す関数 getEvenNum を定義して、実行する例です。

関数 getEvenNum は、非同期処理である setTimeout を使って、発生させた乱数 rand が偶数の場合は成功と判定し、奇数の場合は失敗と判定します。

定義では、成功した場合に resolve() を呼び出し、引数に結果の値として乱数 rand を渡します。失敗した場合は reject() を呼び出し、引数にエラーオブジェクトを渡します。

そして、getEvenNum() を実行して、then() メソッドの第1引数(onFulfilled)に成功した場合のコールバックを、第2引数(onRejected)に失敗した場合のコールバックを登録しています。

以下を実行すると getEvenNum() の引数に指定したミリ秒後に、内部で発生させた乱数 rand が偶数の場合はその値が出力され、奇数の場合はエラーメッセージが表示されます。

JavaScript
function getEvenNum(delay) {
  // Promise オブジェクトを生成して返す
  return new Promise((resolve, reject) => {
    // 非同期処理
    setTimeout(() => {
      // 乱数を生成して変数 rand に代入
      const rand = Math.floor( Math.random() * 10 );
      // rand が偶数であれば成功、奇数であれば失敗と判定
      if(rand % 2 === 0) {
        // 処理が成功した場合は resolve(値) を呼び出す
        resolve(rand);
      }else{
        // 処理が失敗した場合は reject(Error) を呼び出す
        reject(new Error(`偶数ではありません。値:${rand}`))
      }
    }, delay);
  });
}

// 実行例
getEvenNum(300).then(
  // 成功した場合に resolve(値) に渡された「値」を受け取るコールバック
  (value) => {
    console.log(value);
  },
  // 失敗した場合に reject(エラー) に渡された「エラー」を受け取るコールバック
  (error) => {
    console.warn(error.message);
  }
);

エラーの捕捉

Promise が失敗すると(rejected になると)エラーが発生します。

発生したエラー(例外)は then() メソッドの第2引数のコールバック関数、または catch() メソッドにコールバック関数を登録することで捕捉することができます。

上記では then() メソッドの第2引数に失敗した場合のコールバックを指定していますが、以下のように catch() を使って reject() に渡されたエラーを捕捉することができます。

JavaScript
// catch() を使う場合
getEvenNum(300).then(
  // 成功した場合に resolve(値) に渡された「値」を受け取るコールバック
  (value) => {
    console.log(value);
  }
).catch(
  // 失敗した場合に reject(エラー) に渡された「エラー」を受け取るコールバック
  (error) => {
    console.warn(error.message);
  }
);

※ then() メソッドの第2引数のコールバック関数、または catch() メソッドにコールバック関数が登録されていないと、Promise が失敗した場合に例外(エラー)が発生します。

関連ページ(JavaScript Promise の詳細):JavaScript Promise / Async Function の使い方

Promise の型と型注釈

Promise の型はジェネリック型を使って Promise<T> というような型引数を1つ持つ型になります。

型引数 T には、Promise の結果の値(非同期処理が成功したときに返す値)の型を指定します。

以下の Promise の場合、成功すると resolve("Hello!") により、"Hello!"(文字列)が結果の値として返されるので、型引数には string 型を指定します(以下の Promise では成功の場合のみを扱っています)。

const promise = new Promise<string>((resolve) => {
  setTimeout(() => {
    resolve("Hello!");  // 処理が成功した場合の結果の値は "Hello!"
  }, 1000);
});

promise.then((value) => {
  console.log(value);
});

以下のように戻り値の変数に型注釈を指定することもできます。

Promise<string> は string 型の結果(成功したときに返す値)を持つ Promise を表します。

const promise: Promise<string> = new Promise((resolve) => {
  setTimeout(() => {
    resolve("Hello!");
  }, 1000);
});

promise.then((value) => {
  console.log(value);
});

type 文(型エイリアス)を使って別途型を定義しておくこともできます。以下は上記と同じことです。

type StringPromiseType = Promise<string>;

const promise: StringPromiseType = new Promise((resolve) => {
  setTimeout(() => {
    resolve("Hello!");
  }, 1000);
});

上記のいずれの場合も、Promise の型の指定により、then() メソッドの onFulfilled コールバック関数の引数(value)の型は自動的に string と型推論されます(適切に型が推論されます)。

以下のように onFulfilled の引数に明示的に型注釈することもできますが、省略可能です。

promise.then((value: string) => {
  console.log(value);
});

但し、型引数や型注釈を指定しないと、結果の型がうまく推論されず unknown になってしまいます。

以下は、型を省略した場合に、VS Code で戻り値の変数 promise や引数 value にマウスオーバーした際に表示される型推論の結果です。Promise<unknown>value: unknown となっています。

コンストラクタの部分(new Promise)にマウスオーバーすると、以下のように Promise コンストラクタの型の定義が表示され、resolve() に渡される値(value)や Promise の戻り値の型が unknown になっているのが確認できます。

また、型引数に指定した型と返す値の型が合わない場合はコンパイルエラーになります。

const promise = new Promise<string>((resolve) => {
  setTimeout(() => {
    resolve(123);  // コンパイルエラー
  }, 1000);
});

resolve 関数に型注釈

コンストラクタの引数 executor 関数の第1引数(resolve 関数)に型注釈を指定すこともできます。

resolve 関数(executor の第1引数)の戻り値の型は void になります(何も返さない)。

以下の場合も、戻り値の型は Promise<string> になり、value の型は string 型と推論されます。

const promise = new Promise((resolve:(value: string) => void) => {
  setTimeout(() => {
    resolve("Hello!");
  }, 1000);
});

promise.then((value) => {
  console.log(value);
});

以下のように type 文で関数の型を宣言しても同じです。

type ResolveStringType = (value: string) => void;

const promise = new Promise((resolve: ResolveStringType) => {
  setTimeout(() => {
    resolve("Hello!");
  }, 1000);
});

Promise の型定義

Promise の型の定義は node_modules/typescript/lib/ にある型定義ファイルで確認できます。

以下は lib.es5.d.ts の interface Promise の部分(Promise の型定義)です。

lib.es5.d.ts(interface Promise の抜粋)
interface Promise<T> {
    /**
     * Attaches callbacks for the resolution and/or rejection of the Promise.
     * @param onfulfilled The callback to execute when the Promise is resolved.
     * @param onrejected The callback to execute when the Promise is rejected.
     * @returns A Promise for the completion of which ever callback is executed.
     */
    then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): Promise<TResult1 | TResult2>;

    /**
     * Attaches a callback for only the rejection of the Promise.
     * @param onrejected The callback to execute when the Promise is rejected.
     * @returns A Promise for the completion of the callback.
     */
    catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null): Promise<T | TResult>;
}

Promise は非同期操作が完了するか失敗するかを表すオブジェクトです。

then メソッドは成功時の処理を、catch メソッドは失敗時の処理を定義します。上記の型定義では、then メソッドと catch メソッドがそれぞれ成功時と失敗時にどのような型を返すかを指定しています。

then メソッドの型定義

以下が then メソッドの型定義で、ジェネリクスを使って定義されています。

then<TResult1 = T, TResult2 = never>(
  onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>)| undefined| null,
  onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>)| undefined| null
): Promise<TResult1 | TResult2>;

TResult1 と TResult2 はジェネリクスの型パラメータで、実際の型は Promise を生成する際に具体的な型に置き換えられます。いずれもデフォルトが設定されているので、省略可能です。

  • TResult1: then メソッドが成功時に返す値の型。デフォルトは T(Promise の型引数に指定された型)で、Promise 自体が成功すると、元の Promise の型と同じ型を返します。
  • TResult2: then メソッドが失敗時に返す値の型。デフォルトは never で失敗時には何も返さないことを表します。

引数のコールバック onfulfilled と onrejected はいずれもオプショナル(?)として定義されています。

  • onfulfilled?: T 型の引数を受け取り、TResult1 型 または PromiseLike<TResult1> 型の値を返す関数(または undefined | null)
  • onrejected?: any 型の引数を受け取り、TResult2 型 または PromiseLike<TResult2> 型の値を返す関数(または undefined | null)

PromiseLike<TResult1> や PromiseLike<TResult2>は、Promise と同じように振る舞うオブジェクトの型を表しています。

例えば、string 型の結果を持つ Promise<string> の場合、以下のように推論されます。

Promise<string>.then<string, never>(
  onfulfilled?: ((value: string) => string | PromiseLike<string>) |null|undefined,
  onrejected?: ((reason: any) => PromiseLike<never>) | null | undefined
): Promise<string | never>

catch メソッドの型定義

catch メソッドの型定義は次のようになっています。TResult は、catch メソッドが失敗時に返す値の型です。デフォルトは never(失敗時には何も返さない)。

catch<TResult = never>(
  onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null
): Promise<T | TResult>;

Promise コンストラクタや静的メソッドの型は lib.es2015.promise.d.ts で確認できます。

Promise チェーン

then メソッド(及び catch メソッド)は常に新しい Promise インスタンスを返すので、チェーン (連鎖) させることができます。Promise チェーンでは、Promise が失敗しない限り、順番に then メソッドで登録したコールバック関数を呼び出します。

また、Promise チェーンではコールバックで返した値を次のコールバックへ引数として渡せます。渡される引数の型は自動的に推論されます。

// 文字列を結果に持つ Promise を返す関数
function hello(delay: number) {
  return new Promise<string>((resolve) => {
    setTimeout(() => {
      resolve(`Hello (delay: ${delay}ms)`);
    }, delay);
  });
}

// 数値を結果に持つ Promise を返す関数
function randomNum(delay: number) {
  return new Promise<number>((resolve) => {
    setTimeout(() => {
      const rand = Math.floor( Math.random() * 10 );
      resolve(rand);
    }, delay);
  });
}

// それぞれの then メソッドのコールバックの引数の型は自動的に推論される
hello(500).then((result)=> { // result は string 型
  console.log(result.length); // 20(結果の文字列の文字数)
  return hello(1000);
}).then((result)=> { // result は string 型
  console.log(result.toUpperCase());  // HELLO (DELAY: 1000MS)
  return randomNum(1000);
}).then((result)=> { // result は number 型
  console.log(result * 3);  // randomNum(1000)の値 *3
  return false;
}).then((result)=> { // result は boolean 型
  console.log(result); // false
});

上記のように then メソッドのコールバックの引数の型は自動的に推論されるので省略できます(関連:then メソッドの型定義)。

必要に応じて、then<type> のように型引数を使って then メソッドの成功時のコールバック関数 onfulfilled の戻り値の Promise の結果の型を指定することもできます。

失敗時のコールバック関数 onrejected の結果の型(never)も then<type, never> のように指定することができます。※ type には実際の型を指定します。

// then メソッドの戻り値の型を指定する例
hello(500).then<string>((result)=> {
  console.log(result.length);
  return hello(1000);  // string 型の結果を持つ Promise を返す
}).then<number>((result)=> { // result は string
  console.log(result.toUpperCase());
  return randomNum(1000);  // number 型の結果を持つ Promise を返す
}).then<boolean>((result)=> { // result は number
  console.log(result * 3);
  return false; // boolean 型の結果を持つ Promise を返す
}).then<void>((result)=> { // result は boolean
  console.log(result); // 何も返さない
});

例えば以下の場合、戻り値の型 <string | number> を指定しないと、then メソッドで期待される型が一致していないためコンパイルエラーになります(以下は指定しているのでエラーになりません)。

function hello(delay: number) {
  return new Promise<string>((resolve) => {
    setTimeout(() => {
      resolve(`Hello (delay: ${delay}ms)`);
    }, delay);
  });
}

// thenメソッドの戻り値の型 <string | number> を指定しないとコンパイルエラーになる例
hello(500).then<string | number>((result)=> {
  const length = result.length;
  if(length > 5) {
    return hello(1000);
  }else{
    return length;
  }
}).then((result)=> {  // result は string | number
  if(typeof result === "string") {
    console.log(result.toUpperCase());
  }else{
    console.log(result * 10);
  }
});

以下は戻り値の型を指定せずコンパイルエラーになる場合の VS Code でのスクリーンショットです。

エラー内容:型 '(result: string) => number | Promise<string>' の引数を型 '(value: string) => string | PromiseLike<string>' のパラメーターに割り当てることはできません。

Promise を返す関数の型注釈

Promise を返す関数の型注釈は、関数の戻り値に型注釈する方法と Promise のインスタンスを生成する際に型を指定する方法があります。

以下は Promise を返す関数の戻り値に型注釈する例です。

関数 getEvenNum は Promise の中で setTimeout を使用して、引数に指定したミリ秒経過後に乱数を発生させて、その値(rand)が偶数であれば成功とみなして resolve() に乱数の値 rand を渡して呼び出しています。rand が奇数の場合は失敗とみなして reject() にエラーを渡しています。

成功した場合の Promise の結果は rand(数値)なので、戻り値の型は Promise<number> となります。

// 関数の戻り値に型注釈を指定
function getEvenNum(delay:number): Promise<number> {
  // Promise オブジェクトを生成して返す
  return new Promise((resolve, reject) => {
    // 非同期処理
    setTimeout(() => {
      const rand = Math.floor( Math.random() * 10 );
      if(rand % 2 === 0) {
        // 処理が成功した場合
        resolve(rand);
      }else{
        // 処理が失敗した場合
        reject(new Error(`偶数ではありません。値:${rand}`))
      }
    }, delay);
  });
}

// value は number 型と推論される
getEvenNum(300)
  .then((value) => {
    console.log(value);
  })
  .catch((error) => {
    console.warn(error.message);
  });

以下は Promise のインスタンスを生成する際に型を指定する例で、型引数に number を指定しています。

いずれの方法でも、then() のコールバック関数の引数(value)の型が適切に推論されるようになります。

function getEvenNum(delay:number) {
  // Promise のインスタンスを生成する際に型を指定
  return new Promise<number>((resolve, reject) => {
    setTimeout(() => {
      const rand = Math.floor( Math.random() * 10 );
      if(rand % 2 === 0) {
        resolve(rand);
      }else{
        reject(new Error(`偶数ではありません。値:${rand}`))
      }
    }, delay);
  });
}

以下はアロー関数で書き換えたものです。

// 関数の戻り値に型注釈を指定
const getEvenNum = (delay:number): Promise<number> => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const rand = Math.floor( Math.random() * 10 );
      if(rand % 2 === 0) {
        resolve(rand);
      }else{
        reject(new Error(`偶数ではありません。値:${rand}`))
      }
    }, delay);
  });
}

// Promise のインスタンスを生成する際に型を指定
const getEvenNum = (delay:number) => {
  return new Promise<number>((resolve, reject) => {
    setTimeout(() => {
      const rand = Math.floor( Math.random() * 10 );
      if(rand % 2 === 0) {
        resolve(rand);
      }else{
        reject(new Error(`偶数ではありません。値:${rand}`))
      }
    }, delay);
  });
}

以下は指定されたパスの画像が読み込めたら、その img 要素を結果に持つ Promise を返す関数です。

成功した場合の結果の値は img 要素なので、Promise<HTMLImageElement> としています。

function loadimage(src: string) {
  // Promise オブジェクトを生成し返す
  return new Promise<HTMLImageElement>((resolve, reject) => {
    const img = document.createElement('img');
    img.src = src;
    // 画像のロードに成功した場合:resolve を呼び出して結果(img)を引数に渡す
    img.onload = () => resolve(img);
    // 失敗した場合:reject を呼び出してエラーオブジェクトを引数に渡す
    img.onerror = () => reject(new Error(`Error: Image Not Found "${src}".`));
  });
}

//then メソッドを使用
loadimage('images/sample.jpg').then(
  // img: HTMLImageElement
  (img) => {console.log(`height:${img.height} / width:${img.width}`)}
).catch((error) => {
  // error: any
  console.warn(error.message);
});
失敗時のコールバック関数の引数の型

Promise が失敗した場合、then メソッドの onRejected や catch() メソッドのコールバックの引数の型は、デフォルトでは any です。

必要に応じて、例えば以下のようにコールバックの引数の型を明示的に指定することもできます。

getEvenNum(300)
  .then((value: number) => {
    console.log(value);
  })
  .catch((error: Error) => {
    console.warn(error.message);
  });

以下は失敗時に catch メソッドで受け取る値(error)の型を unknown とする例です。unknown 型はそのままでは使えないので、instanceof を使って Error オブジェクトとして絞り込んでいます。

getEvenNum(300)
  .then((value) => {
    console.log(value);
  })
  .catch((error: unknown) => {
    if (error instanceof Error) {
      console.warn(error.message);
    }
  });
必ず失敗(reject)する Promise の型

以下の delayedReject は引数に指定されたミリ秒後に必ず失敗する Promise を返します。

この Promise は必ず失敗する(成功することはない)ので結果の型は never としています。

Promise<never> は never 型の結果を持つ Promise(成功することはない Promise)を表します。

これにより、この関数で生成した Promise に対して then メソッドを呼び出すと、onFulfilled コールバック関数の引数は never 型になり、never 型の値が得られるコードは決して実行されることはないので、onFulfilled が呼び出されることがない(成功することはない)ことを表すことができます。

const delayedReject = (delay: number) => {
  return new Promise<never>((resolve, reject)=> {
    setTimeout(()=> {
      reject(new Error('Rejected!'));
    }, delay)
  })
}

delayedReject(1000).catch((e) => {
  console.warn(e.message);  // 1秒後に「Rejected!」と出力
});

// 以下でも同じこと
delayedReject(2000).then(undefined, (e) => {
  console.warn(e.message);  // 2秒後に「Rejected!」と出力
});

delayedReject(3000).then((result) => {
  console.log(result);  // result は never 型(ここは決して呼び出されない)
}).catch((e) => {
  console.warn(e.message);  // 3秒後に「Rejected!」と出力
});

上記の delayedReject は以下のように戻り値の型に Promise<never> を指定しても同じです。

const delayedReject = (delay: number): Promise<never> => {
  return new Promise((resolve, reject)=> {
    setTimeout(()=> {
      reject(new Error('Rejected!'));
    }, delay)
  })
}

関連項目:Promise.reject

静的メソッド

Promise.resolve

Promise.resolve は与えられた引数を結果として即座に成功する(Fulfilled の状態の) Promise オブジェクトを作成するメソッドです。

Fulfilled の状態となった Promise インスタンスに then メソッドで登録したコールバック関数は、常に非同期なタイミングで実行されます。以下の場合「同期的な処理」の出力の後に「123」が出力されます。

const promise = Promise.resolve(123);

promise.then((result) => {
    console.log(result);  // 123(非同期なタイミングで実行される)
});

console.log("同期的な処理");

/* 出力
同期的な処理
123
*/

Promise.resolve の場合、引数に渡す結果となる値の型は自動的に推論されます。上記の場合、戻り値は Promise<number>、result は number 型と推論されます。

そのため型引数の指定は省略できますが、以下のように明示的に型引数を指定することもできます。

const promise = Promise.resolve<number>(123);

または、戻り値の Promise を代入する変数に型注釈を指定することもできます。

const promise: Promise<number> = Promise.resolve(123);

Promise.resolve は lib.es2015.promise.d.ts にジェネリクスを使った型が定義されています。

Awaited<T> は Promise が解決されたときの値の型を取得するユーティリティ型です。

lib.es2015.promise.d.ts(一部抜粋)
resolve<T>(value: T): Promise<Awaited<T>>;

また、Promise.resolve メソッドは new Promise の糖衣構文(シンタックスシュガー)なので、上記は以下のように記述したのと同じことです。

const promise = new Promise<number>((resolve) => {
  resolve(123);
});
Promise.reject

Promise.resolve は与えられた引数を結果として即座に失敗する(Rejected の状態となった) Promise オブジェクトを作成するメソッドです。

Promise.resolve 同様、Rejected の状態となった Promise インスタンスに then や catch メソッドで登録したコールバック関数は、常に非同期なタイミングで実行されます。以下の場合「同期的な処理」の出力の後に「エラー発生」が出力されます。

const promise = Promise.reject(new Error("エラー発生"));

promise.catch((e) => {
  console.log(e.message); //エラー発生(非同期なタイミングで実行される)
});

console.log("同期的な処理");

/* 出力
同期的な処理
エラー発生
*/

catch メソッドの代わりに、then メソッドに失敗した場合のコールバック関数だけを登録することもできます(以下は上記の3〜5行目と同じこと)。

promise.then(undefined, (e) => {
  console.log(e.message); //エラー発生(非同期なタイミングで実行される)
});

Promise.reject の場合、決して成功することはないので、Promise の型引数(非同期処理が成功したときに返す値の型)はデフォルトでは never になっています(決して使われることはないため)。

また、onRejected の引数に渡す値(失敗の理由)の型は any になります。必要に応じて明示的に型を指定することができます。

const promise = Promise.reject<never>(new Error("エラー発生"));

promise.catch((e: unknown) => {
  if(e instanceof Error){
    console.log(e.message);
  }
});

Promise.reject も lib.es2015.promise.d.ts にジェネリクスを使った型が定義されています。

lib.es2015.promise.d.ts(一部抜粋)
reject<T = never>(reason?: any): Promise<T>;

また、Promise.reject メソッドは Promise.resolve 同様、new Promise のシンタックスシュガーなので、上記は以下のように記述したのと同じことです。

const promise = new Promise<never>((resolve, reject) => {
  reject(new Error("エラー発生"));
});
Promise.all

Promise.all メソッドは Promise インスタンスの配列を受け取り、それらが全て成功したら、成功となる新しい Promise を1つ作成して返します。但し、1つでも失敗した場合は、残りの処理は実行せず、その時点で失敗の Promise(rejected の Promise)が返ります。

Promise.all を使うことで複数の Promise を使った非同期処理を1つの Promise として扱えます。

then メソッドで登録した成功時のコールバック関数が受け取る引数は全ての Promise インスタンスの結果をまとめた配列で、その要素の並び順は Promise.all に渡した Promise インスタンスの順番になります。

以下の関数 delayedPrint は引数で指定された name と delay の値を含む文字列を結果として持つ Promise<string> 型の Promise を返します。

返される Promise は第3引数 rejected に true が指定されない限り、指定された秒数後に成功(解決)します。第3引数 rejected はデフォルト引数を設定してあるので、引数の型注釈を省略できます。

以下では delayedPrint を使っていずれも成功する3つの Promise を作成し、それらを Promise.all に配列で渡して戻り値の新しい Promise を変数 promiseAll に代入しています。

この例の場合、変数 promiseAll に代入された新しい Promise は Promise<[string, string, string]> のようなタプル型を型引数に持つ型に推論されます。これは3要素(この場合はいずれも string 型)のタプル型の結果を持つ Promise を表します。

then メソッドに登録した成功時のコールバック関数が受け取る引数(results)は Promise.all に渡されたそれぞれの Promise の成功時の結果が同じ順番で入った結果の配列になります。

// 文字列を結果として持つ Promise を返す関数
function delayedPrint(name: string, delay: number, rejected=false) {
  return new Promise<string>((resolve, reject) => {
    setTimeout(() => {
      if(rejected) {
        // 第3引数が true の場合は、失敗させる
        reject(new Error(`失敗 ${name}:${delay}ms`));
      }
      // 成功したら指定された name と delay を含む文字列を結果の値として返す
      resolve(`${name}: ${delay}ms`);
    }, delay)
  });
}

// Promise オブジェクトのインスタンスを作成
const one = delayedPrint('1', 1500);
const two = delayedPrint('2', 500);
const three = delayedPrint('3', 1000);

// Promise.all に Promise オブジェクトの配列を渡して、新しい Promise を作成
const promiseAll = Promise.all([one, two, three]);

// Promise.all により作成された Promise の結果を出力
promiseAll.then((results) => {
  console.log(results); // ['1: 1500ms', '2: 500ms', '3: 1000ms']
});

Promise.all に渡す個々の Promise や Promise.all の戻り値は必ずしも変数に入れる必要はないので以下のようにも記述できます。また、以下では then の成功時のコールバック関数の引数を分割代入で受け取っています。

Promise.all([
  delayedPrint('1', 1500),
  delayedPrint('2', 500),
  delayedPrint('3', 1000)
]).then(([one, two, three]) => {  // 配列の分割代入
  console.log(one);   // 1: 1500ms
  console.log(two);   // 2: 500ms
  console.log(three); // 3: 1000ms
});

Promise.all に渡された Promise のうちどれか1つでも失敗すると、その時点で Promise.all は失敗した Promise を返します。

Promise.all([
  delayedPrint('1', 1500),
  delayedPrint('2', 500),
  delayedPrint('3', 1000, true) // 失敗するプロミス
]).then((results) => {
  console.log(results);
}).catch((e) => {
  console.log(e.message);  // 失敗 3:1000ms
});

関連ページ: JavaScript Promise.all

Promise.allSettled

Promise.allSettled は Promise.all 同様、Promise インスタンスの配列を受け取り、新しい Promise インスタンスを返します。

Promise.all の場合は、渡されたいずれかの Promise が失敗すると新しい Promise も失敗になりますが、Promise.allSettled は渡された全ての Promise が解決(成功または失敗)するまで待ち、渡された個々の Promise の結果に関わらず成功します。

Promise.allSettled の返り値の結果は、Promise.all 同様、配列ですが、Promise.allSettled の場合、配列の各要素は以下のようなオブジェクトになり、成功したかどうかは status プロパティで判断できます。

  • 成功した場合: {status:"fulfilled", value:結果の値}
  • エラーの場合: {status:"rejected", reason:結果の値}

以下は Promise.allSettled の使用例です。

Promise.allSettled が返す新しい Promise は失敗することはなく、渡されたすべての Promise が拒否されても catch に移らないので、promise.allSettled に catch は通常、必要ありません。

以下の場合、2つ目の Promise が0.5 秒後に失敗して reject されますが、すべての Promise の結果が出てから新しい Promise が返されます。

// 文字列を結果として持つ Promise を返す関数
function delayedPrint(name: string, delay: number, rejected=false) {
  return new Promise<string>((resolve, reject) => {
    setTimeout(() => {
      if(rejected) {
        reject(new Error(`失敗 ${name}:${delay}ms`));
      }
      resolve(`${name}: ${delay}ms`);
    }, delay)
  });
}

const promiseAllSettled = Promise.allSettled([
  delayedPrint('1', 1500),
  delayedPrint('2', 500, true), // 失敗するプロミス
  delayedPrint('3', 1000)
]);

promiseAllSettled.then(([one, two, three]) => {  // 引数の配列を分割代入
  console.log(one);   // {status: 'fulfilled', value: '1: 1500ms'}
  console.log(two);   // {status: 'rejected', reason: Error: 失敗 2:500ms }
  console.log(three); // {status: 'fulfilled', value: '3: 1000ms'}
});

上記の変数 promiseAllSettled の型は以下のような型に推論されます。

Promise<[PromiseSettledResult<string>,PromiseSettledResult<string>,PromiseSettledResult<string>]>;

改行すると以下のようになります。

Promise<[
  PromiseSettledResult<string>,
  PromiseSettledResult<string>,
  PromiseSettledResult<string>
]>

これはこの Promise の結果の型が3要素(いずれも string を型引数に持つ PromiseSettledResult 型)のタプル型であることを表しています。各要素の PromiseSettledResult 型は lib.es2020.promise.d.ts で以下のように定義されています。

lib.es2020.promise.d.ts
interface PromiseFulfilledResult<T> {
  status: "fulfilled";
  value: T;
}

interface PromiseRejectedResult {
  status: "rejected";
  reason: any;
}

type PromiseSettledResult<T> = PromiseFulfilledResult<T> | PromiseRejectedResult;

PromiseSettledResult<T>PromiseFulfilledResult<T>PromiseRejectedResult のユニオン型になっていて、 status プロパティをタグとして使うタグ付きユニオンの構造になっています。

成功と失敗を区別するには、各 Promise の結果のオブジェクトの status で区別することができます。

const promiseAllSettled = Promise.allSettled([
  delayedPrint('1', 1500),
  delayedPrint('2', 500, true), // 失敗するプロミス
  delayedPrint('3', 1000)
]);

promiseAllSettled.then((results) => {
  // 成功した Promise の配列
  const fulfilled = results.filter(obj => obj.status === 'fulfilled');
  console.log(fulfilled);
  /* [
    {"status": "fulfilled","value": "1: 1500ms"},
    {"status": "fulfilled","value": "3: 1000ms"}
  ] */

  // 失敗した Promise の配列
  const rejected = results.filter(obj => obj.status === 'rejected');
  console.log(rejected);
 /* [
    { "status": "rejected","reason": {Error: 失敗 2:500ms}}
  ] */
});
Promise.race

Promise.race は Promise.all と同様、Promise インスタンスの配列を受け取り、新しい Promise インスタンスを返しますが、いずれか1つの Promise インスタンスの処理が完了した時点で、返された新しい Promise インスタンスは、配列の中で一番最初に完了した Promise インスタンスと同じ状態になります。

最初に完了した処理の結果(失敗の場合はエラー)が新しい Promise インスタンス(Promise.race 全体)の結果になります。

以下の場合、Promise.race は2つ目の Promise の処理が完了する時点(500ミリ秒後)に確定し、then メソッドはその処理の結果を受け取ります。

この例の場合、Promise.race により作成されて変数 promise に代入された新しい Promise は Promise<string> という型に推論されます。

function delayedPrint(name: string, delay: number, rejected:boolean=false) {
  return new Promise<string>((resolve, reject) => {
    setTimeout(() => {
      if(rejected) {
        reject(new Error(`失敗 ${name}:${delay}ms`));
      }
      resolve(`${name}: ${delay}ms`);
    }, delay)
  });
}

const promise = Promise.race([
  delayedPrint('1', 1500),
  delayedPrint('2', 500),
  delayedPrint('3', 1000)
]);

promise.then((result) => {
  console.log(result);  // 2: 500ms
}).catch((e) => {
  console.log(e.message);
});

以下の場合は、最初に処理が確定する2つ目の Promise が失敗するので、全体の結果はエラーとなり catch メソッドのコールバックが呼ばれます。

Promise.race([
  delayedPrint('1', 1500),
  delayedPrint('2', 500, true), // 失敗するプロミス
  delayedPrint('3', 1000)
]).then((result) => {
  console.log(result);
}).catch((e) => {
  console.log(e.message); // 失敗 2:500ms
});

以下の場合、エラーが発生して失敗する3つ目の処理が実行される前に、2つ目の処理が確定するので、その時点(500ミリ秒後)で then メソッドのコールバック関数はその処理の結果を受け取ります。

Promise.race([
  delayedPrint('1', 1500),
  delayedPrint('2', 500),
  delayedPrint('3', 1000, true) // 失敗するプロミス
]).then((result) => {
  console.log(result); // 2: 500ms
}).catch((e) => {
  console.log(e.message);
});
Promise.any

Promise.any は Promise.race 同様、Promise インスタンスの配列を受け取り、新しい Promise を返しますが、渡された Promise のいずれかが成功した時点で Promise.any の Promise も成功になります。

Promise.race は成功も失敗も含めて一番早く結果がでたものをその結果としますが、Promise.any は成功したもののうち一番早いものを結果とします。

以下の場合、2番目の promise2 が一番早いですが、失敗する(reject される)ため、その次に早い3つ目の promise3 が結果になります。

また、この例の場合、Promise.any により作成されて変数 promise に代入された新しい Promise は Promise<string> という型に推論されます。

// 文字列を結果として持つ Promise を返す関数
function delayedPrint(name: string, delay: number, rejected=false) {
  return new Promise<string>((resolve, reject) => {
    setTimeout(() => {
      if(rejected) {
        reject(new Error(`失敗 ${name}:${delay}ms`));
      }
      resolve(`${name}: ${delay}ms`);
    }, delay)
  });
}

const promise = Promise.any([
  delayedPrint('1', 1500),
  delayedPrint('2', 500, true), // 失敗するプロミス
  delayedPrint('3', 1000)
]);

promise.then((result) => {
  console.log(result);  // 3: 1000ms(成功したもののうち一番早いもの)
}).catch( (error) =>{
  // 失敗したときのコールバック
  console.log(error.message);
});

すべて失敗する場合

Promise.any に渡された Promise が全て失敗した場合は、Promise.any により作成された新しい Promise の結果も失敗になり、AggregateError というエラーオブジェクトで reject されます。

以下の場合、渡された Promise はすべて失敗するので AggregateError で reject され、catch に移ります。AggregateError には message、name、errors プロパティがあります。

function delayedPrint(name: string, delay: number, rejected=false) {
  return new Promise<string>((resolve, reject) => {
    setTimeout(() => {
      if(rejected) {
        reject(new Error(`失敗 ${name}:${delay}ms`));
      }
      resolve(`${name}: ${delay}ms`);
    }, delay)
  });
}

const promise = Promise.any([
  delayedPrint('1', 1500, true), // 失敗するプロミス
  delayedPrint('2', 500, true),  // 失敗するプロミス
  delayedPrint('3', 1000, true)  // 失敗するプロミス
]).then((result) => {
  console.log(result);  // これは実行されない
}).catch((error) =>{
  console.log(error.message);  // All promises were rejected
  console.log(error.name); // AggregateError
  console.log(error.errors[0]); // Error: 失敗 1:1500ms
  console.log(error.errors[1]); // Error: 失敗 2:500ms
  console.log(error.errors[2]); // Error: 失敗 3:1000ms
});

async/await

Promise ベースの非同期処理をより簡潔に書くための async 関数await 式が用意されています。

async 関数

async キーワードを関数の前に付けることで、その関数は Promise オブジェクトを返す async 関数になります。以下は async 関数の定義例です。

async function asyncDouble(x: number): Promise<number> {
  return x * 2;
}

const promise = asyncDouble(3); // 確認のため async 関数の戻り値を変数に代入
console.log(promise);  // Promise {<fulfilled>: 6}

promise.then(result => {
  console.log(`result: ${result}`);  // result: 6
});

async 関数の戻り値は必ず Promise になります。

但し、その async 関数が return する値により、返される Promise は以下のように異なります。

  • return される値が Promise でない場合
    return された値を結果に持つ Promise が fulfilled になって(解決された状態で)返されます。言い換えると、return された値が戻り値の Promise の結果となります。
  • return される値が Promise の場合
    return された Promise をそのまま返します。
  • 例外が発生した場合
    その例外(エラー)を結果に持つ rejected になった Promise を返します。

上記の場合、async 関数 asyncDouble で return される値は Promise ではく数値なので、戻り値は数値を結果に持つ Promise が(解決された状態で)返されます。

そのため、asyncDouble() の戻り値の型注釈は Promise<number> としています。

※ async 関数の戻り値の型は自動的に推論されるので、戻り値の型注釈は省略することができます。

上記で定義した async 関数 asyncDouble() は以下のように定義してもほぼ同じことです。

以下は Promise.resolve() を使って即座に成功する Promise オブジェクトを返しています。この場合、返される Promise.resolve() の型は推論されるので、async 関数の戻り値の型も自動的に推論されます。

async function asyncDouble(x: number) {
  return Promise.resolve(x * 2);
}

上記は以下と同じことです。但し、この場合、return する Promise の型引数 <number> を省略すると、async 関数の戻り値の型は正しく推論されず、Promise<unknown> となってしまいます。

async function asyncDouble(x: number) {
  return new Promise<number>((resolve) => {
    resolve(x * 2);
  })
}

return する Promise の型引数を指定せずに、以下のように async 関数の戻り値の型注釈を付けることもできます。

async function asyncDouble(x: number): Promise<number>  {
  return new Promise((resolve) => {
    resolve(x * 2);
  })
}

関数式やアロー関数で定義

async 関数は関数式やアロー関数でも async キーワードを前に付けるだけで定義できます。

以下はいずれも同じことです(戻り値の型注釈は省略できます)。

// 関数宣言
async function asyncDouble(x: number): Promise<number> {
  return x * 2;
}

// 関数式
const asyncDouble = async function(x: number): Promise<number> {
  return x * 2;
}

// アロー関数
const asyncDouble = async (x: number): Promise<number> => {
  return x * 2;
}

async メソッド

メソッドの場合も、async キーワードをメソッドの前に付けるだけです。

class AsyncCalc {
  // async メソッド(戻り値の型注釈は省略可能)
  async double(x: number): Promise<number> {
    return x * 2;
  }
}

const asyncCalc = new AsyncCalc(); // インスタンスを作成
const promise = asyncCalc.double(3); // 確認のため async メソッドの戻り値を変数に代入
console.log(promise);  // Promise {<fulfilled>: 6}

promise.then(result => {
  console.log(`result: ${result}`);  // result: 6
});

メソッドでアクセス修飾子をつける場合は async の前につけます。

class AsyncCalc {
  // アクセス修飾子は async の前に付ける
  public async double(x: number): Promise<number> {
    return x * 2;
  }
}

実行中に例外が発生した場合

async 関数の中で例外が発生した場合は、その結果(エラー)を持つ rejected な Promise を返すので、戻り値の Promise が失敗します。

例外を捕捉するには catch メソッドや then メソッドの第2引数のコールバックを使います。

async function throwError() {
  throw new Error('Error !!!');
}

const promise = throwError(); // 確認のため async 関数の戻り値を変数に代入
console.log(promise); // Promise {<rejected>: Error: Error !!! ...}

promise.catch((e: Error) => {
  console.log(e.message);  // Error !!!
});

// 以下でも同じ
promise.then( undefined, (e: Error) => {
  console.log(e.message); // Error !!!
});

値を返さない場合

async 関数の中で何も値を返さない場合は undefined の結果を持つ Promise を返すのと同じことになります。同期的な処理は実行されます。

この場合、何も返さないので Promise<void> として型注釈できますが、省略可能です。

async function asyncFunc(): Promise<void> {
  // 何も値を返さない
  console.log('hello');  // 同期的なタイミングで出力される
}

const promise = asyncFunc();
console.log(promise);  // Promise {<fulfilled>: undefined}

promise.then(value => {
  console.log(value); // undefined
});

await 式

await は async 関数の中で動作するキーワードで、指定した Promise の値が解決されるまで実行を待機し、解決された値を返します。

基本的に await は async 関数の中でしか使えませんが、ES2022 からはモジュールのトップレベル(関数の外)において await を利用することができます(トップレベル await)。

await は与えられた式(通常は Promise インスタンス)の評価結果を値として返します。

以下の async 関数は指定された秒数後に解決する Promise を返す関数を await に指定しています。

async 関数の実行は await が指定されている行(13行目)で一時停止し、delayed_hello() により生成される Promise が Fulfilled または Rejected になるまで待って Promise が確定したときに再開します。

Promise が確定すると、変数 result には Promise の結果の値(Hello! 2000ms passed)が入り、次の行でコンソールへ出力されます。

以下の場合、asyncFunc() の実行により2秒後に「 Hello! 2000ms passed」と出力されます。

// 指定された秒数後に文字列を結果に持ち成功する Promise を返す関数
function delayed_hello(delay: number) {
  return new Promise<string>((resolve) => {
    setTimeout(() => {
      resolve(`Hello! ${delay}ms passed `);
    }, delay)
  })
}

// async 関数
async function asyncFunc(delay: number): Promise<void> {
  // await を指定(非同期処理が完了したら結果の値が返される)
  const result = await delayed_hello(delay) ;
  // 上記の処理が完了したら以下が実行される
  console.log(result);
}

asyncFunc(2000);  // Hello! 2000ms passed

上記の async 関数 asyncFunc は戻り値がないので Promise<void> として型注釈していますが、自動的に推論されるので省略することができます。

以下の async 関数 main の中では await を3回使って delayedDouble の戻り値を取得しています。

await が指定されている行では一時停止し、Promise が確定したときに再開して次の行へ移ります。

そのため、main() を実行後 result1 が出力されるのは1秒後、result2 が出力されるのは、それから2秒後(開始から3秒後)、result3 が出力されるのは、それから3秒後(開始から6秒後)になります。

async 関数が最後まで実行された場合、そのタイミングで async 関数が返した Promise が解決されます。

この場合、合計値(result1 + result2 + result3)が返されると main() の戻り値の Promise が解決して、then のコールバックに Promise の結果(合計値)が渡され、total: 12 と出力されます。

async 関数 main は数値を結果に持つ Promise を返すので Promise<number> として型注釈していますが、自動的に推論されるので省略することができます。

function delayedDouble(x: number, delay: number) {
  return new Promise<number>((resolve) => {
    setTimeout(() => {
      resolve(x * 2);
    }, delay)
  })
}

async function main(): Promise<number> {
  const result1 = await delayedDouble(1, 1000);
  console.log(`result1: ${result1}`);  // result1: 2(1秒後)
  const result2 = await delayedDouble(2, 2000);
  console.log(`result2: ${result2}`);  // result2: 4(3秒後)
  const result3 = await delayedDouble(3, 3000);
  console.log(`result3: ${result3}`);  // result3: 6(6秒後)
  // 数値を返す(数値を結果に持つ Promise が返される)
  return result1 + result2 + result3;
}

const promise = main();  // 戻り値の Promise を確認するために変数に代入
console.log(promise);  // Promise の状態は時間の経過により変化

promise.then((total) => {
  console.log(`total: ${total}`); // total: 12(約6秒後)
});

上記20行目の main() の戻り値の Promise のコンソールへの出力は、Promise が解決する前に確認すると、以下のように状態は pending で結果の値は undefined になっています。

ブラウザを再読込して、Promise が解決した後に確認すると、以下のように状態は fulfilled で結果の値は 12 になっています。

async/await を使うことで、ある非同期処理が完了したら次の非同期処理を行うというプログラムを同期プログラムのように簡潔に書くことができます。

以下はほぼ同じことを async/await を使わずに then を使って書き換えた例です。

function delayedDouble(x: number, delay: number) {
  return new Promise<number>((resolve) => {
    setTimeout(() => {
      resolve(x * 2);
    }, delay)
  })
}

let total = 0;
const promise = delayedDouble(1, 1000).then((result)=> {
  total += result;
  console.log(`result1: ${result}`);  // result1: 2(1秒後)
  return delayedDouble(2, 2000);
}).then((result)=> {
  total += result;
  console.log(`result2: ${result}`);  // result2: 4(3秒後)
  return delayedDouble(3, 3000);
}).then((result)=> {
  total += result;
  console.log(`result3: ${result}`);  // result3: 6(6秒後)
  return total;
}).then((total)=> {
  console.log(`total: ${total}`); // total: 12(約6秒後)
  return total; // promise の結果の値を 12 とする(指定しない場合は結果の値は undefined)
});

console.log(promise); // Promise の状態は時間の経過により変化
エラー処理

await に指定した Promise が rejected となった場合、await 式はその位置で例外を投げ、async 関数は rejected な Promise を返します。

以下の関数 randomResult は指定されたミリ秒後に発生させた乱数の値が偶数であればその乱数の値を結果に持つ Promise を返し、乱数の値が奇数の場合は失敗した rejected な Promise を返します。

async 関数 luckyNumber は await を使って randomResult の結果の値(乱数)を表示します。

但し、luckyNumber() を実行して randomResult で 発生させた乱数の値(rand)が奇数の場合は8行目の reject() により、rejected な Promise が返され、例外(エラー)が発生します。

function randomResult(delay: number) {
  return new Promise<number>((resolve,reject) => {
    setTimeout(() => {
      const rand = Math.floor( Math.random() * 10 );
      if(rand % 2 === 0) {
        resolve(rand);
      }else{
        reject(new Error(`失敗`));
      }
    }, delay);
  });
}

// 戻り値の型注釈: Promise<void> は省略可能
async function luckyNumber(): Promise<void> {
  console.log('checking ...');
  const result = await randomResult(1000);
  console.log(`Lucky Number: ${result}`);
}

luckyNumber();

async 関数で例外を捕捉する1つの方法は、以下のように luckyNumber() の戻り値の Promise の catch() メソッドで rejected な Promise(失敗した Promise)を処理します。

// catch() メソッドで rejected な Promise を受け取る
luckyNumber().catch((error)=> {
  console.log(`catch() メソッドで捕捉:${error.message}`)
});

// randomResult(1000) の戻り値の Promise が失敗した場合の出力例
// checking ...
// catch() で捕捉:失敗

try-catch で例外を捕捉

または、await に指定した Promise が失敗すると await 式が例外を投げるので、以下のように async 関数の中でその例外を try-catch 構文でキャッチすることができます。

catch ブロックで渡されるエラー(以下の場合は e)は unknown 型になるので、message プロパティにアクセスするには instanceof Error で Error オブジェクトのインスタンスであることを確認する必要があります。

// async 関数の中で例外を try-catch で捕捉(戻り値の型注釈は省略)
async function asyncFuncTC() {
  try {
    console.log('checking ...');
    const result = await randomResult(1000);
    console.log(`Lucky Number: ${result}`);
  } catch(e) {
    // e は unknown 型なので e instanceof Error で確認して e.message にアクセス
    if (e instanceof Error) {
      console.log('例外捕捉: ' + e.message);
    }
  }
}

asyncFuncTC();

// randomResult(1000) の戻り値の Promise が失敗した場合の出力例
// checking ...
// 例外捕捉: 失敗

上記の場合、async 関数の中で例外を捕捉しているので、async 関数 asyncFuncTC は普通(正常)に終了し、戻り値の Promise は必ず成功します。そのため、以下の then() メソッドに登録したコールバックは、randomResult(1000) が失敗しても成功しても必ず実行されます。

※ catch() メソッドが呼び出されることはありません。

asyncFuncTC().then(() => {
  console.log('async 関数終了');
});
// asyncFuncTC() が返す Promise は失敗しないので、catch() が呼ばれることはない

以下は await randomResult(1000) が成功した場合はその値(number 型)を返すように asyncFuncTC を書き換えたものです。

この場合、返す値は例外を捕捉する際に分岐するので、asyncFuncTC の戻り値の Promise の型はユニオン型になります。async 関数の定義の中で何も値を返さない場合は undefined の結果を持つ Promise を返すのと同じことになるので戻り値の型を Promise<number | undefined> としています。

省略した場合、自動的に Promise<number | undefined> と推論されるので、実際には省略可能です。

async function asyncFuncTC(): Promise<number | undefined> {
  try {
    console.log('checking ...');
    const result = await randomResult(1000);
    console.log('Lucky Number が出ました!');
    // number 型を返す
    return result;
  } catch(e) {
    // 何も返さない(void)→ undefined の結果を持つ Promise を返す
    if (e instanceof Error) {
      console.log('例外捕捉: ' + e.message);
    }
  }
}

asyncFuncTC().then((result) => {
  // result が undefined でなければ(result は number 型または undefined)
  if(result) {
    console.log(`Lucky Number: ${result}`);
  }else{
    console.log('残念でした。')
  }
});

// await randomResult(1000) が成功した場合
// checking ...
// Lucky Number が出ました!
// Lucky Number: 6

// await randomResult(1000) が失敗した場合
// checking ...
// 例外捕捉: 失敗
// 残念でした。

上記の場合、then のコールバックに渡される result は number 型または undefined のユニオン型になるので、result を使用する際は型の絞り込みが必要になります。

Awaited<T>

Awaited<T> は、TypeScript 4.5 で追加された TypeScript のユーティリティ型の一つで、Promise の型を抽出するのに使用します(Promise が解決されたときの値の型を取得することができます)。

例えば、以下のような Promise の解決時の値の型を知りたい場合、Awaited<T> を使用すれば解決時の型(以下の場合は string)を取得できます。

const myPromise = new Promise<string>((resolve) => {
  setTimeout(() => {
    resolve("Hello!");
  }, 1000);
});

type Result = Awaited<typeof myPromise>;  // string

Awaited<T> は内部的には、T が Promise である場合は Promise の解決時の型を取得し、そうでない場合はそのままの型を返します。

つまり、Awaited<T> の型パラメータに Promise<T> があれば、その T を取得することができます。

また、以下の type B のように Promise<T> がネストしていても解決してくれるます。

type C の場合のように、Promise<T> ではない boolean 型 はそのままの型を得られます。

type A = Awaited<Promise<string>>;  // type A は string

type B = Awaited<Promise<Promise<number>>>;  //type B は number

type C = Awaited<boolean | Promise<number>>;  //type C は number | boolean

await 式のような Promise を解決する際の値の型を抽出するのに利用できます。

例えば、以下の fetchData() の戻り値の型は Promise<{ name: string; age: number }> です。

この Promise の結果の値の型は以下のように取得できます。

ReturnType<typeof fetchData> は fetchData() の戻り値の型を取得し、その型に対して Awaited<T> を使用して Promise が解決されたときの型を取得しています。

ReturnType<T> も TypeScript のユーティリティ型の一つで、関数の戻り値の型を返します。

async function fetchData(url: string): Promise<{ name: string; age: number }> {
  const response = await fetch(url);
  const data = await response.json();
  return data;
}

// fetchData() の戻り値の Promise の結果の値の型
type FetchDataResult = Awaited<ReturnType<typeof fetchData>>;
// FetchDataResult の型は { name: string; age: number }

fetch

fetch() は外部リソースや API などにアクセスするためのメソッド(関数)で、指定した URL から非同期処理としてデータを取得します。

fetch() メソッドにリソースの URL を指定して呼び出すとリクエストが発行され、Response オブジェクトを結果に持つ Promise オブジェクトが返されます。

例えば、以下のような構成で file.txt や users.json にアクセスしてデータを取得してみます。

ディレクトリ構成
tsp // プロジェクトディレクトリ
├── dist
│   └── index.js  // コンパイルされた JavaScript
├── file.txt    // アクセスするファイル(テキストファイル)
├── users.json  // アクセスするファイル(JSON ファイル)
├── index.html  // dist/index.js(コンパイルされた JavaScript)を読み込む
├── node_modules
├── package-lock.json
├── package.json
├── src
│   └── index.ts  // TypeScript
└── tsconfig.json
file.txt
hello from file.txt

以下は file.txt にアクセスしてデータを取得する例です。

fetch() メソッドは Response オブジェクトを結果に持つ Promise を返すので、戻り値の Promise インスタンスを代入する変数の型注釈には Promise<Response> を指定することができます。

実際には、自動的に推論されるので戻り値の型注釈:Promise<Response>は省略することができます。

VS Code などを使っていれば、変数などにマウスオーバーすると推論される型が表示されるので、確認することができます。

最初の then() メソッドのコールバックの引数に渡される response は Response オブジェクトで、これも Response 型と自動的に推論されます。

response.text() は文字列として解析した値を結果に持つPromise<string>型の Promise を返すので、続く then() メソッドのコールバックの引数に渡される data は自動的に string 型と推論されます。

index.ts
// Response オブジェクトを結果に持つ Promise が返される
const promise: Promise<Response> = fetch('file.txt');

// then() メソッドで処理
promise.then((response) => {
  // response を文字列として解析した値を結果に持つ Promise を返す
  return response.text();  // Promise<string>型の Promise を返す
})
.then((data) => {
  // response.text()が解決すると、解析されたテキスト(data)が渡される
  console.log(data);  // hello from file.txt と出力される
});

例えば、以下のように fetch() メソッドの戻り値をコンソールに出力してみると、戻り値は結果の値に Response オブジェクトを持つ Promise であることが確認できます。

index.ts
const promise = fetch('file.txt');
console.log(promise);

以下のように出力されます。

node コマンドでファイルを実行して確認するには、fetch() に指定する URL は上記のような相対パスでの指定ではエラーになってしまうようです。試してみたところ、上記のコンソールの出力にある url の値を使って、例えば、以下のように指定するとエラーになりませんでした。

index.ts
const promise = fetch('http://127.0.0.1:5500/tsp/file.txt');
console.log(promise);
node コマンドにファイル( dist/index.js)を指定して実行
% node dist/index.js
Promise { <pending> }

次は以下のようなオブジェクトの配列を記述した JSON ファイルにアクセスしてデータを取得してみます。

users.json(オブジェクトの配列)
[
  {
    "name": "foo",
    "age": 24
  },
  {
    "name": "bar",
    "age": 36
  }
]

この例では fetch() メソッドに then() メソッドをチェーンしています。

fetch() が成功すると Response オブジェクトを結果に持つ Promise が返されます。

then() メソッドのコールバックでは json() メソッドで JSON として解析した結果で解決する Promise を作成して返し、続く then() メソッドのコールバックで JSON に解決した値(data)を出力しています。

index.ts
fetch('users.json').then((response) => {
  // JSON として解析した結果で解決する Promise を返す
  return response.json();
})
.then((data) => {  // data は any 型
  // JSON として解析された結果(data)を出力
  console.log(data);  //[{name: 'foo', age: 24},  {name: 'bar', age: 36}]
  console.log(data[0].name);  // foo
});

上記のコードはコンパイルエラーにはなりませんが、response.json() の戻り値の Promise が解決された値(then のコールバックに渡される data)の型は any 型と推論されます。

TypeScript コンパイラーは、fetch() の戻り値の結果を json()メソッドで解析した JSON オブジェクトがどのような型かを知らないので、明示的に型の情報を伝える必要があります。

以下では取得するデータのオブジェクトの型 User を定義して、then のコールバックの中で型注釈に使用しています(取得するデータは User 型オブジェクトの配列)。

response.json() は User 型の要素から成る配列を結果に持つ Promise を返すので、response.json() の戻り値を代入する変数 result の型を Promise<User[]> としています。

9,10 行目は型アサーション as を使って1行で記述することもできます。

index.ts
// 取得するデータのオブジェクトの型(User 型)を定義
type User = {
  name: string;
  age: number;
}

fetch('users.json').then((response) => {
  // response.json() の戻り値に型注釈を付ける
  const result: Promise<User[]> = response.json();
  return result;
  // または型アサーション as を使って以下でもほぼ同じ
  // return response.json() as Promise<User[]>;
})
.then((data) => {
  console.log(data);
  if(data[0]) {
    console.log(data[0].name);
  }
});

以下のように then メソッドの成功時のコールバック関数の戻り値の結果の型を then<User[]> のように指定しても、data の型は User[] と推論されます。但し、result の型は Promise<any> と推論されます。

// then メソッドの成功時のコールバック関数の戻り値の結果の型を指定
fetch('users.json').then<User[]>((response) => {
  const result = response.json();  // result の型は Promise<any>
  return result;
})
.then((data) => {  // data の型は User[]
  console.log(data);
  if(data[0]) {
    console.log(data[0].name);
  }
});

または、then メソッドの成功時のコールバック関数の戻り値に (response): Promise<User[]> のように型注釈しても上記とほぼ同じです。

// then メソッドの成功時のコールバック関数の戻り値に型注釈
fetch('users.json').then((response): Promise<User[]> => {
  const result = response.json();   // result の型は Promise<any>
  return result;
})
.then((data) => {  // data の型は User[]
  console.log(data);
  if(data[0]) {
    console.log(data[0].name);
  }
});

上記のいずれかの方法で data の型は User[] であることがコンパイラに伝わります。

async 関数を作成

以下は前述の例を async 関数を使って書き換えた例です。

fetchUsers() はファイル(users.json)からデータを取得して、その値(User 型オブジェクトの配列)を結果に持つ Promise を返す async 関数です。

User 型オブジェクトの配列の型は User[] となるので、async 関数 fetchUsers() の戻り値の型注釈を Promise<User[]>としています(以下の場合、戻り値の型注釈は省略可能)。

await は指定した Promise の値が解決されるまで処理を待機し、解決されたら値を返します。

以下の async 関数の定義では、await に fetch() を指定して、その結果(Response オブジェクト)を変数 response に代入しています。

続いて await に response.json() を指定して、その結果の値を変数 data に代入しています。

変数 data は JSON として解析した結果(User 型の配列)なので、型注釈を User[] とします。

fetchUsers() は取得したデータを結果に持つ Promise を返すので、then() メソッドのコールバックを使ってデータを取り出すことができます。

index.ts
// 取得するデータのオブジェクトの型(User 型)を定義
type User = {
  name: string;
  age: number;
}

// users.json からデータを取得する async 関数
async function fetchUsers(): Promise<User[]> {
  const response = await fetch('users.json');
  const data: User[] = await response.json();
  return data;
  // または return await response.json() as Promise<User[]>;
}

// fetchUsers() を実行して then() メソッドを使ってデータを取得
fetchUsers().then((data) => {
  if(data[0]) {
    console.log(data[0].name.toUpperCase()); // FOO
    console.log(data[0].age -10); // 14
  }
});

エラーハンドリング

ネットワークエラーが発生した場合は、reject された Promise が返されるので、catch() メソッドや then() メソッドの第2引数のコールバックでエラーを捕捉することができます。

以下は、{JSON} Placeholder というテスト用の JSON データを返してくれる無料の API サービスにアクセスしてデータを取得する例です。ネットワークエラーが発生して reject された Promise が返された場合は、catch() メソッドでエラーを補足してメッセージを出力します。

index.ts
// アクセスする URL
const url = 'https://jsonplaceholder.typicode.com/posts/1';

// 取得するデータの型を定義
type Post = {
  userId: number;
  id: number;
  title: string;
  body: string;
}

fetch(url).then((response) => {
  const result: Promise<Post> = response.json();
  return result;
})
.then((data) => {
  console.log(data.title);
})
//エラーの場合の処理
.catch((error: Error) => {
  console.log(error.message);
});

但し、Promise はネットワーク障害または CORS 違反など許可の問題によってのみ拒否されるため、404 や 500 などの HTTP エラーは拒否されません。

そのため、上記のコードでは指定したファイルが存在しない場合(404)やサーバーエラー(500)などリクエストが正常に終了しない場合には、エラーとして捕捉することができません。

ok プロパティでリクエストが成功したかどうかを判定

リクエストが成功(正常に終了)したかどうかは Response オブジェクトの ok プロパティで判定する必要があります(関連項目:404 Not Found や 500 エラー)。

Response オブジェクトの ok プロパティは、HTTP ステータスコードが 200〜299 の範囲(成功レスポンス)であれば true を返し、それ以外の400番台や500番台であれば false を返します。

以下は ok プロパティが false の場合(リクエストが正常に終了しない場合)は、例外を投げるように書き換えたコードです。投げられた例外は catch() メソッドで捕捉できます。

fetch(url).then((response) => {
  // ステータスが ok でなければ例外を投げる
  if(!response.ok) {
    throw new Error(`リクエスト失敗: ${response.status}`);
  }
  const result: Promise<Post> = response.json();
  return result;
})
.then((data) => {
  console.log(data.title);
})
//エラーの処理(ステータスが ok でない場合)
.catch((error) => {
  console.log(error.message);
});

async 関数の場合

以下は上記を async 関数を使って書き換えた例です。戻り値の型注釈 :Promise<Post> は省略可能です。

async function fetchPost(url: string): Promise<Post> {
  const response = await fetch(url);
    // ステータスが ok でなない場合は例外を投げる
    if (!response.ok) {
      throw new Error(`リクエスト失敗: ${response.status}`);
    }
    // ステータスが ok であればデータを取得して返す(戻り値の変数を Post 型に注釈)
    const data: Post = await response.json();
    return data;
}

fetchPost(url).then((data) => {
  // data は Post 型
  console.log(data.title);
})
//エラーの処理
.catch((error) => {
  console.log(error.message);
});

以下は try-catch 構文を使ってエラー処理を実装する例です。

上記の例同様、async 関数の戻り値の型注釈 : Promise<Post|undefined> は省略可能です。

async 関数が返す値は例外を捕捉する際に分岐するので、fetchPost の戻り値の型はユニオン型になります。そのため、then メソッドのコールバックの引数 data を使用する際は型の絞り込みが必要になります。

また、catch ブロックで渡される error は unkown 型になるので、message プロパティにアクセスするには error が Error オブジェクトのインスタンスであることを確認する必要があります。

エラーは async 関数内で捕捉されるため、ステータスに関わらず、then() メソッドの成功時のコールバックが呼び出されます。この場合、catch() メソッドを記述してもそのコールバックが呼び出されることはありません(catch ブロックで例外を投げるか rejected な Promise を返せば呼び出されます)。

async function fetchPost(url: string): Promise<Post|undefined> {
  try {
    const response = await fetch(url);
    // ステータスが ok でなない場合は例外を投げる
    if (!response.ok) {
      throw new Error(`リクエスト失敗: ${response.status}`);
    }
    // ステータスが ok であればデータを取得して返す
    const data: Post = await response.json();
    return data;
  } catch (error) {
    // エラーの処理
    if (error instanceof Error) { // error は unknown 型なので絞り込みが必要
      console.log(error.message);
    }
  }
}

// ステータスに関わらず成功時のコールバックが呼び出される
fetchPost(url).then((data) => {
  // data が undefined でなければ data にアクセス
  if(data) {
    console.log(data.title);
  }
  // 以下はステータスに関わらず必ず実行される
  console.log('完了');
});

ジェネリクスの利用

今までの例の場合、関数の定義で取得するデータの型を直接コードに記述していため、データの型が異なると別途関数を定義する必要がありました。

ジェネリクスを利用すると取得するデータの型が異なる場合でも同じ関数で処理することができます。

以下の fetchJsonData はアクセスする URL を引数に、取得するデータの型を型引数 <T> に受け取り、JSON 形式のデータを取得する async 関数です。

async function fetchJsonData<T>(url: string): Promise<T> {
  const response = await fetch(url);
  if (!response.ok) {
    throw new Error(`リクエスト失敗: ${response.status}`);
  }
  const data: T = await response.json();
  return data;
}

以下では {JSON} Placeholder という無料の API(https://jsonplaceholder.typicode.com/todos/1)にアクセスしてデータ(Todo 型のオブジェクト)を取得してその値をコンソールへ出力します。

fetchJsonData() は取得したデータを結果に持つ Promise を返すので、then メソッドのコールバック関数を使って取得したデータを出力しています。

また、リクエストがエラーになった場合には catch() メソッドでエラー処理するようにしています。

// アクセスする URL
const url = 'https://jsonplaceholder.typicode.com/todos/1';

// 取得するデータの型 Todo を定義
type Todo = {
  userId: number;
  id: number;
  title: string;
  completed: boolean;
}

// URL と取得するデータの型(Todo)を指定して then のコールバックで処理を定義
fetchJsonData<Todo>(url).then((data) => {
  // data は Todo 型の値
  console.log(`  id: ${data.id}
  title: ${data.title}
  completed: ${data.completed}
  User Id: ${data.userId}`);
}).catch((error) => {
  // エラー処理
  console.warn(error.message);
});;

/* 出力
id: 1
title: delectus aut autem
completed: false
User Id: 1
*/

関数にエラー処理を追加

前述の例では、リクエストがエラーになった場合は catch() メソッドでエラー処理するようにしていますが、以下はデータを取得する async 関数で try-catch 構文を使って例外を捕捉する例です。

この場合、データを取得する関数 fetchJsonData の戻り値の型は Promise<T | undefined> となり、then メソッドのコールバックに渡されるデータの型は T | undefined のユニオン型になるので、オブジェクトのメンバーにアクセスするには undefined でないことを確認する必要があります。

async function fetchJsonData<T>(url: string): Promise<T | undefined> {
  // try-catch 構文を使って例外を捕捉
  try {
    const response = await fetch(url);
    if (!response.ok) {
      throw new Error(`リクエスト失敗: ${response.status}`);
    }
    const data: T = await response.json();
    return data;
  } catch (error) {
    // error は unknown 型なので Error のインスタンスであることを確認
    if (error instanceof Error) {
      console.log(error.message);
    }
  }
}

// アクセスする URL
const url = "https://jsonplaceholder.typicode.com/posts/1";

// 取得するデータの型を定義
type Post = {
  userId: number;
  id: number;
  title: string;
  body: string;
};

// URL と取得するデータの型を指定して then のコールバックで処理を定義
fetchJsonData<Post>(url).then((data) => {
  // data が undefined でなければ
  if (data) {
    // data は Post 型
    console.log(`title: ${data.title}
body: ${data.body}`);
  }
});

/* 出力
title: sunt aut facere repellat provident occaecati excepturi optio reprehenderit
body: quia et suscipit
suscipit recusandae consequuntur expedita et cum
reprehenderit molestiae ut ut quas totam
nostrum rerum est autem sunt rem eveniet architecto
*/

POST リクエストの送信

fetch メソッドを使用して POST リクエストを送信することができます。

POST メソッドでリクエストを送信する場合は、fetch メソッドの第2引数 options の method オプションに POST を指定し、body オプションに送信するリクエストの本文を指定します。

以下は JSON データ(JSON 形式のオブジェクト)を POST メソッドで送信する例です(いろいろな書き方ができると思いますが以下のその一例です)。

headers オプションには JSON 形式のデータを送るので、Content-Type に application/json を指定し、body オプションで送信するデータを JSON.stringify() で JSON 形式の文字列に変換しています。

Fetch オプションの設定のオプションのオブジェクト reqOptions の型注釈に指定している RequestInit は fetch に渡す設定やオプションを指定するためのオブジェクト型です。

// リクエストのURL
const url = "https://jsonplaceholder.typicode.com/posts";

// POST するデータ
const postData = {
  title: "foo",
  body: "Lorem ipsum dolor sit",
  userId: 1,
};

// Fetch オプションの設定(RequestInit はオプションのためのオブジェクト型)
const reqOptions: RequestInit = {
  // POST を設定
  method: "POST",
  headers: {
    // データの形式に合わせて Content-Type を設定
    "Content-Type": "application/json",
    // 他に必要なヘッダーがあれば追加
  },
  // データを JSON 形式の文字列に変換して設定
  body: JSON.stringify(postData),
};

// レスポンス(取得するデータ)の型
type ResType = {
  title: string;
  body: string;
  userId: number;
  id: number;
};

// fetch を使用して POST リクエストを送信
fetch(url, reqOptions)
  .then((response) => {
    if (!response.ok) {
      throw new Error(`リクエスト失敗: ${response.status}`);
    }
    const result: Promise<ResType> = response.json(); // 応答をJSON形式で解析
    return result;
    // 上記の2行は return response.json() as Promise<ResType>; でもほぼ同じ
  })
  .then((data) => {
    console.log('Success:', data);
    console.log(data.title, "\n", data.body);
  })
  .catch((error) => {
    console.warn("Error:", error);
  });

/* 出力(擬似的なレスポンス)
Success: {title: 'foo', body: 'Lorem ipsum dolor sit', userId: 1, id: 101}
foo
Lorem ipsum dolor sit
*/

上記の例では {JSON} Placeholder という API を利用していますが、実際に POST リクエストが処理されるわけではなく、擬似的なレスポンスを返してくれます。

以下は上記を async 関数とジェネリクスを使って書き換えた例です。型引数 T にはレスポンス(取得するデータ)の型を指定します。

async function postData<T>(url:string, options:RequestInit):Promise<T | undefined> {
  try {
    const response = await fetch(url, options);
    if (!response.ok) {
      throw new Error(`リクエスト失敗: ${response.status}`);
    }
    const data: T = await response.json();
    return data;
  } catch (error) {
    if (error instanceof Error) {
      console.log(error.message);
    }
  }
}

// 使用例
const url = "https://jsonplaceholder.typicode.com/posts";

// POST するデータ
const requestData = {
  title: "bar",
  body: "Recusandae voluptatem",
  userId: 2,
};

// Fetch オプションの設定
const reqOptions: RequestInit = {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
  },
  body: JSON.stringify(requestData),
};

// レスポンス(取得するデータ)の型
type ResType = {
  title: string;
  body: string;
  userId: number;
  id: number;
};

// 定義した関数を実行
postData<ResType>(url, reqOptions).then((data) => {
  if(data) {
    console.log('Success:', data);
    console.log(data.title, "\n", data.body);
  }
});

/* 出力(擬似的なレスポンス)
Success: {title: 'bar', body: 'Recusandae voluptatem', userId: 2, id: 101}
bar
Recusandae voluptatem
*/

以下はデータを取得するだけの場合にも対応できるように、第2引数をオプショナルに書き換えたものです。

また、第1引数の型を RequestInfo として、URL の文字列または Request オブジェクトを受け取れるようにしています。

async function fetchData<T>(
  resource: RequestInfo, // 文字列または Request オブジェクト
  options?: RequestInit // オプショナル
): Promise<T | undefined> {
  try {
    const response = await fetch(resource, options);
    if (!response.ok) {
      throw new Error(`リクエスト失敗: ${response.status}`);
    }
    const data: T = await response.json();
    return data;
  } catch (error) {
    if (error instanceof Error) {
      console.log(error.message);
    }
  }
}

使用する際は、前述の例のように第1引数に文字列で URL を指定し、第2引数にオプションのオブジェクトを渡すこともできますし、以下のように第1引数に URL のみを指定してデータを取得することもできます。

// レスポンスで取得するオブジェクトの型(レスポンスは配列)
type ResType = {
  title: string;
  body: string;
  userId: number;
  id: number;
};

// 第1引数に URL を指定してデータのみを取得する例(第2引数は省略)
fetchData<ResType[]>("https://jsonplaceholder.typicode.com/posts").then((data) => {
  if (data && data[0]) {
    console.log(data[0].title, "\n", data[0].body);
  }
});

また、以下のように第1引数に Request オブジェクトを指定することもできます。

// POST するデータ
const requestData = {
  title: "bar",
  body: "Recusandae voluptatem",
  userId: 2,
};

// Request オブジェクトを作成
const request = new Request("https://jsonplaceholder.typicode.com/posts", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
  },
  body: JSON.stringify(requestData),
});

// レスポンスで取得するオブジェクトの型
type ResType = {
  title: string;
  body: string;
  userId: number;
  id: number;
};

// Request オブジェクトを引数に指定して実行
fetchData<ResType>(request).then((data) => {
  if (data) {
    console.log("Success:", data);
    console.log(data.title, "\n", data.body);
  }
});

fetch() の型定義

fetch() 関数の型は node_modules/typescript/lib/lib.dom.d.ts で以下のように定義されています。

lib.dom.d.ts(抜粋)
declare function fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;

この型定義は以下のようなことを表しています。

  • fetch 関数は、1つまたは2つの引数を受け取ります。
  • 第1引数 input は取得するリソースを指定するための RequestInfo 型か、URL オブジェクトです。
  • 第2引数 init は、リクエストの設定を指定するための RequestInit 型で、省略可能です。
  • 関数は Promise<Response> を返します。つまり、非同期にリソースを取得し、結果は Response オブジェクトとして解決される Promise になります。
RequestInit

RequestInit は Fetch API で使用するオプションの型注釈で、fetch() に渡す設定やオプションを指定するためのオブジェクト型(interface)です。

node_modules/typescript/lib/lib.dom.d.ts で以下のように定置されています。

これらのプロパティはすべてオプショナルで、必要に応じて使用します。

lib.dom.d.ts(抜粋)
interface RequestInit {
  /** A BodyInit object or null to set request's body. */
  body?: BodyInit | null;
  /** A string indicating how the request will interact with the browser's cache to set request's cache. */
  cache?: RequestCache;
  /** A string indicating whether credentials will be sent with the request always, never, or only when sent to a same-origin URL. Sets request's credentials. */
  credentials?: RequestCredentials;
  /** A Headers object, an object literal, or an array of two-item arrays to set request's headers. */
  headers?: HeadersInit;
  /** A cryptographic hash of the resource to be fetched by request. Sets request's integrity. */
  integrity?: string;
  /** A boolean to set request's keepalive. */
  keepalive?: boolean;
  /** A string to set request's method. */
  method?: string;
  /** A string to indicate whether the request will use CORS, or will be restricted to same-origin URLs. Sets request's mode. */
  mode?: RequestMode;
  /** A string indicating whether request follows redirects, results in an error upon encountering a redirect, or returns the redirect (in an opaque fashion). Sets request's redirect. */
  redirect?: RequestRedirect;
  /** A string whose value is a same-origin URL, "about:client", or the empty string, to set request's referrer. */
  referrer?: string;
  /** A referrer policy to set request's referrerPolicy. */
  referrerPolicy?: ReferrerPolicy;
  /** An AbortSignal to set request's signal. */
  signal?: AbortSignal | null;
  /** Can only be null. Used to disassociate request from any Window. */
  window?: null;
}

以下は RequestInit の主要なプロパティとその説明です。

プロパティ 説明(すべてオプショナル)
method リクエストの HTTP メソッド('GET' や 'POST' など)を指定します。
headers リクエストヘッダーを指定するオブジェクトです。各ヘッダーのキーと値を含む連想配列として定義します。
body リクエストのボディ部分のデータを指定します。通常は文字列や FormData などが入ります。JSON データを送信する場合は、JSON.stringify で JSON 文字列に変換します。
mode リクエストのモードを指定します。例えば、'cors'や'no-cors'などがあります。デフォルトは 'no-cors' です。
credentials 認証情報の送信方法を指定します。例えば、'same-origin'や'include'があります。
cache リクエストのキャッシュポリシーを指定します。例えば、'default'や'no-store'があります。
RequestInfo

RequestInfo も Fetch API で使用される型の一つで、fetch メソッドに URL や Request オブジェクト、またはそれらを生成するための文字列などを渡す際に、複数の型を受け入れる型です。

node_modules/typescript/lib/lib.dom.d.ts で以下のように定置されています。

lib.dom.d.ts(抜粋)
type RequestInfo = Request | string;

RequestInfo 型は Request オブジェクトまたは URL の文字列を受け入れることができます。

以下は async 関数の定義で引数の型注釈に RequestInfo を使用する例です。

これにより、引数には文字列の URL や Request オブジェクトを指定することができます。

// 引数の型注釈に RequestInfo を利用
async function fetchData<T>(url: RequestInfo): Promise<T | undefined> {
  try {
    const response = await fetch(url);
    if (!response.ok) {
      throw new Error(`リクエスト失敗: ${response.status}`);
    }
    const data: T = await response.json();
    return data;
  } catch (error) {
    if (error instanceof Error) {
      console.log(error.message);
    }
  }
}

const url = "https://jsonplaceholder.typicode.com/posts";

type Post = {
  userId: number;
  id: number;
  title: string;
  body: string;
};

// URL を直接渡す場合
fetchData<Post[]>(url).then((data) => {
  if (data && data[0]) {
    console.log(data[0]);
  }
});

// Request オブジェクトを使用する場合
const request = new Request(url, {
  method: "GET",
  headers: {
    "Content-Type": "application/json",
  },
});

// Request オブジェクトを指定
fetchData<Post[]>(request).then((data) => {
  if (data && data[1]) {
    console.log(data[1]);
  }
});