【TypeScript】非同期処理について徹底解説!!(Promise/async・await)

【TypeScript】非同期処理について徹底解説!!(PromiseAsync・await) フロントエンド
スポンサーリンク

はじめに

この記事で分かること
  • 非同期処理とは何か?
  • TypeScriptでの非同期処理の実装方法
ぴんくうさぎ
ぴんくうさぎ

JavaScript、TypeScriptを勉強し始めたのだけど、

非同期処理がよくわからない!!

みどりがめ
みどりがめ

非同期処理は簡単にいうと前の処理終了待たず実行処理だよ!

今回は、非同期処理について詳しく学んでいこう!

同期処理と非同期処理

同期処理・・・順番に処理を実行していく(前の処理が終わらないと次の処理が始まらない)

非同期処理・・・前の処理の終了を待たずに処理を実行していく(前の処理が終わらなくても次の処理が実行される)


例えば、以下の3つの工程がある処理を行うとします。

処理①→処理②(実行に3秒かかる)→処理③

この処理が同期処理の場合、必ず①、② 、③の順番で実行されます。
一方で非同期処理の場合、②の処理の完了を待たずに③の処理が実行されます。

非同期処理の最も典型的な例が「時間がかかる処理」です。APIをCallする通信部分、ファイルの読み書き部分などが代表的です。

簡単な具体例を見てみましょう。以下のコードを実行してみてください。

console.log('処理①');
setTimeout(() => {
  console.log('処理②');
}, 3000);
console.log('処理③');

処理①
処理③
処理②
と表示されると思います。
setTimeoutは第2引数で指定した時間だけ待ち、処理を非同期的に行います。

Promiseを使う

PromiseはES2015で追加された非同期処理のための機能です。
Promiseによる非同期処理では、非同期処理を行う関数はPromiseのオブジェクトを返します。
返却されたPromiseオブジェクトに対して、thenやcatchで後続処理やエラーハンドリングを行います。

Promiseにあるfsを使用したサンプルを見てみましょう。

import { readFile } from 'fs/promises';

readFile('sample.txt', 'utf-8')
  .then((data) => {
    console.log('成功', data);
  })
  .catch((error: unknown) => {
    console.log('失敗', error);
  })
  .finally(() => {
    console.log('処理終了');
  });

readFileにより返却される型はPromise<string>となっています。このようにPromiseオブジェクトはPromise<T>という型を持ちます。今回のPromise<string>は「string型の結果を持つPromiseオブジェクト」という意味です。

ファイルの読み込み処理が成功した場合、成功という文字と合わせてファイルの中身が表示されます。
ファイルの読み込み処理が失敗した場合、失敗という文字と合わせてエラーを表すオブジェクトが表示されます。

自分でPromiseオブジェクトを作る例を見てみましょう。

new Promise<string>((resolve) => {
  setTimeout(() => {
    resolve('成功');
  }, 3000);
}).then((data) => {
  console.log(data);
});

この例を実行すると3秒後に「成功」と表示されます。Promiseコンストラクタは一つの型引数と一つの引数を持ちます。今回は型引数としてstringが使用され、引数は(resolve) => {}の関数になります。

Promiseオブジェクトの並行処理

  • Promise.all
    並列処理している全ての処理が終了したら後続処理を行います
const p = Promise.all([
  readFile('sample1.txt', 'utf-8'),
  readFile('sample2.txt', 'utf-8'),
  readFile('sample3.txt', 'utf-8'),
]);

p.then((result) => {
  console.log('成功', result);
}).catch((error) => {
  console.log('失敗', error);
});
  • Promise.race
    並行処理している処理の何れかが完了したら後続処理を行います
const p = Promise.race([
  readFile('sample1.txt', 'utf-8'),
  readFile('sample2.txt', 'utf-8'),
  readFile('sample3.txt', 'utf-8'),
]);

p.then((result) => {
  console.log('成功', result);
}).catch((error) => {
  console.log('失敗', error);
});

async/awaitを使う

asyncとawaitはPromiseをさらに書きやすく、使いやすくするためのものです。

async

asyncを使って宣言された関数はPromiseのオブジェクトを返却します。
関数宣言の先頭にasyncを付与します。

以下のコードは一見、成功という文字列を返すだけのように見えますが返り値の型がPromise<string>となっています。async関数により返り値はPromiseをなります。

async function getSuccess(): Promise<string> {
  return '成功';
}

const p = getSuccess();
p.then((result) => {
  console.log('成功', result);
});

await

awaitはasync関数の中で使える構文です。記述することでPromiseを返却する関数の非同期処理が終わるまで後続の処理の実行を制御することができます。
関数の中でawaitが記されている場合、必ずasyncが記述されていないとエラーになります。
また、awaitで受けられるものはPromiseのインスタンスのみとなります。

以下のサンプルを実行すると3秒後に「成功」と表示されます。このようにawaitを使うとasync関数の実行が一時中断します。ただし、awaitによる一時中断はasync関数が中断しただけで、他の処理には影響がありません。

const sleep = (duration: number) => {
  return new Promise<void>((resolve) => {
    setTimeout(resolve, duration);
  });
};

async function getSuccess(): Promise<string> {
  await sleep(3000);
  return '成功';
}

const p = getSuccess();
p.then((result) => {
  console.log('成功', result);
});

awaitの返り値

awaitは式でありPromiseの結果を返します。Promiseの結果は本来thenメソッドで得るものですが、async関数内ではawait式を使うことでPromiseの結果を得ることができます。これはawaitが「Promiseを待つ」という働きを持ちthenの代わりとなるためです。

async function getFive(): Promise<number> {
  return 5;
}

async function main() {
  const num1 = await getFive();
  const num2 = await getFive();
  const num3 = await getFive();
  return num1 + num2 + num3;
}

main().then((result) => console.log(result));

終わりに

本記事はここまでとなります。

ご覧いただきありがとうございました。ご指摘等がございましたら頂けますと嬉しいです。
引き続き、プログラミングについて定期的に発信していきますのでよろしくお願いします!
また、もしよろしければtwitterもフォローしていただけると嬉しいです!🐢

コメント