非同期処理

最終更新: 2022年01月15日

非同期処理とは

JavaScriptに限らずプログラミングは通常コードの上から下に順番に処理を実行していきます。つまり下の方に書かれたコードは前のコードがすべて終わるのを待ってから実行されます。

もし途中で10分ほど処理に時間のかかるコードがあったとしたら、それ以降のすべての処理は少なくとも10分以上待たないと実行されません。その状態を回避するために非同期通信が存在します。

非同期通信は自分の処理が終わるのを待たずに以降のコードを実行することができます。仮に10分かかる処理があったとしてもそれが非同期通信であれば、それ以降のコードはそれを待たずに順番に実行されます。

遅い処理が同期処理だった場合
action1(); // はじめに実行される action2(); // action1の次に実行される slowAction(); // 10分かかる処理🔥 action3(); // slowActionを10分待った後に実行される⏳ action4(); // action3の後に実行される⏳ action5(); // action4の後に実行される⏳
遅い処理が非同期処理だった場合
action1(); // はじめに実行される action2(); // action1の次に実行される asyncSlowAction(); // 10分かかる処理🔥 action3(); // asyncSlowActionの処理を待たずにすぐ実行される action4(); // action3の後に実行される action5(); // action4の後に実行される

ネットワーク通信を行う fetch などは通常サーバーからのレスポンス(応答)を待つ必要があるため、非同期処理として振る舞うようになっています。これにより後続の処理が通信処理を待つ必要がなくなります。

しかし逆に結果を受け取ってから実行したいシーンではどうすれば良いでしょう?非同期処理には then(処理成功時に実行)、catch(処理失敗時に実行)、finally(成功失敗とわず処理終了時に実行)の3つのメソッドを持ちます。これらを使うことで非同期処理が終わったタイミングで結果を持って別の処理を実行することができます。

非同期処理の扱い

非同期処理は一般的にPromise(プロミス)と呼ばれるオブジェクトになります。Promiseは後述する thencatch などのメソッドを持ち、それらを使うことで非同期処理の結果を受け取ったり、処理が失敗した際のエラー対応をおこなったりします。

then, catch, finallyで結果を待つ

たとえばデータベースからユーザーデータを取得する getUser という非同期処理を行う関数があったとします。非同期処理の結果を受けて何かしたい場合以下のように書きます。

getUser() // 処理成功時に走る .then(user => console.log(user)) // 処理失敗時に走る .catch(err => console.log(err)) // 処理終了時に走る .finally(() => console.log('処理終了'))

ここで注意が必要なのが、非同期通信である時点で処理自体は次の処理が優先されるため、上記のコード以降の処理は thencatchfinally より先に走るということです。

let user; // 処理終了後、上記変数にユーザーデータを代入 getUser().then(result => user = result); console.log(user); // undefined

上記の場合最後の console.log は直前の非同期処理が終わるのを待つわけではないので、ここが走る時点では user は未代入の状態です。ユーザーの中身を console.log で出力したい場合 then の中にコードを記述し、処理が終わった後に実行されるよう調整する必要があります。

async await を使う

もう一つの方法は await を使う方法です。async がついた関数内では非同期処理に await をつけることができます。await がついた非同期処理は処理が完了するまで待機してからそれ以降のコードを実行します。この書き方は then catch 形式よりシンプルな見通しになるため現在主流となっています。

const action = async () => { const user = await getUser(); // ユーザーを取得するまで待ってから次の行を実行する console.log(user); // ユーザーが代入されている } action();

この書き方でエラーハンドリング(エラー時の対応)を行う場合以下のように書きます。

try { const user = getUser(); console.log(user); } catch(err) { console.log(err); } finally { console.log('処理終了'); }

複数の非同期処理(Promise)をまとめて処理する

たとえばユーザー3人のデータをリクエスト(非同期処理)して、全員の結果が揃ったタイミングで何かをしたいシーンがあります。 Promise.all に非同期処理のリストを渡すと、それらの処理をすべて待ってから結果を配列として受け取ることができます。Promise.allの結果はPromise同様 then catchasync await で処理できます。

// これらの結果をすべて待ちたい const task1 = getUser('1'); const task2 = getUser('2'); const task3 = getUser('3'); Promise.all([task1, task2, task3]) .then(users => console.log(users)); // [user1, user2, user3] // あるいは const users = await Promise.all([task1, task2, task3]); console.log(users); // [user1, user2, user3]

Promiseを作る

今では非同期処理はライブラリやAPI、各ツールのSDKが内部的に作成するため我々が自分でPromiseを作るシーンはほとんどありませんが、必要に迫られた時のためにPromiseの作り方を解説します。

// Promise(非同期処理)の作成 const getUser = (shouldSuccess) => return new Promise((resolve, reject) => { setTimeout(() => { if (shouldSuccess) { resolve('山田太郎'); } else { reject('データを取得できませんでした'); } }, 5000); }) } // 作成したPromiseを処理する→成功する getUser(true).then(user => console.log(user)); // 作成したPromiseを処理する→失敗する getUser(false).catch(err => console.log(err));

上記の例はPromiseを使って getUser 的な処理を模擬的に作ったものです。関数が実行されると5秒まってから結果が返却されます。

Promiseは new Promise で作成し、その引数に resolvereject を引数にとる関数を設定します(コールバック関数といいます)

resolve は処理の成功を意味し、データを含めて実行者に応答します。 reject は失敗を意味し、エラー内容を含めて実行者に応答します。

それぞれ resolvethen に渡され、 rejectcatch に応答が伝わります。

参考リソース