【JavaScript】初心者OK!非同期処理について分かりやすく解説します!

JavaScript

【JavaScript】初心者OK!非同期処理について分かりやすく解説します!

なやむくん
非同期処理と同期処理とは?

なやむさん
非同期処理を行う事で何ができるのかを知りたい。

などの疑問や悩みを解決してまいります。

JavaScript学習者がぶつかる壁の1つとして非同期処理が当てはまるのではないのでしょうか。

開発においてとても重要な役割を持つ非同期処理ですが、少々ややこしく初学者は敬遠したくなりがちです。

今回は非同期処理を学びたい方、学びたいと考えているが「難しそう」と敬遠している方に向けて説明していきたいと思います。

みつた
非同期処理は開発おいて必須と言えます!ぜひこの記事でマスターしてください!

非同期処理とは

非同期処理とは別に同期処理というものがあるので、先に同期処理から説明します。

同期処理とは、順番に処理を実行していくことを言います。

つまり1つの処理が実行し終えるまでは、次の処理に移行しないということです。

対して非同期処理は、1つの処理が終了するのを待つことなく、別の処理を実行することができます。

なやむくん
難しいです…。
みつた
日常生活に例えて説明していきますね!

例えば煮込み料理を作るとします。

煮込みハンバーグ

同期処理では煮込んでいる間は何もすることができません。煮込みが完了してからようやく別の作業ができるようになります。

それに対して非同期処理は、煮込んでいる間に料理に使った調理器具を洗ったり、キッチンを掃除することができます。

よろこびくん
非同期処理だと作業を同時に行う事ができるから効率よく進めることができますね!

JavaScriptはシングルスレッドである

JavaScriptは上から順に1つずつ処理を実行していくことしかできません。

つまり複数の処理を同時に行う事ができないのです。

みつた
先程の煮込み料理の例だと、料理が煮込み終わるまでその場で見ていることしかできないイメージですね。

例えば以下のコードを見てください。

console.log("1つ目の処理");
console.log("2つ目の処理");
console.log("3つ目の処理");
console.log("4つ目の処理");

コンソールに値を出力するだけの処理を4つ記述しています。

この実行結果は以下です。

// 実行結果

1つ目の処理
2つ目の処理
3つ目の処理
4つ目の処理

このように上から順に処理が実行されているのが分かるかと思います。

みつた
処理を実行すると処理速度が速くてどれも同じタイミングで実行されていると思いますが、実際には上から順に1つずつ処理が実行されています!

これがシングルスレッドです。

非同期処理をする方法

ここからは非同期処理をする方法について説明していきます。

setTimeout

setTimeoutは、非同期処理を行う事ができるWebAPIのうちTimers APIに分類される一部です。

setTimeoutを使用することで、その処理の開始時間を設定することができます。

実際に使用したコードは以下です。

console.log("1つ目の処理");
console.log("2つ目の処理");
setTimeout(() => console.log("3つ目の処理"), 3000); // 3秒後に処理が実行
console.log("4つ目の処理");

こちらを実行すると以下のような結果になります。

// 実行結果

1つ目の処理
2つ目の処理
4つ目の処理
3つ目の処理

このように1つ目、2つ目は通常通り処理が実行されていますが、次に処理が完了したのは4つ目の処理で、そのあと遅れて3つ目の処理が完了してコンソールに表示されたかと思います。

これはsetTimeout処理を3秒後に実行させる指定をしているからです。

そのためsetTimeoutが実行された処理よりも、先に4つ目の処理が実行されたため処理順序が変更になっているということになります。

Promise 

Promiseとは、非同期処理を行う事ができるオブジェクトで、非同期処理をより簡単に、そして可読性が上がるように書くことができます。

Promiseでは非同期処理の一連の流れを以下のように記述することができます。

new Promise(function(resolve, reject) {
  // ここに非同期処理を記述する
}).then(function(result) {
  // 成功時の処理を記述する
}).catch(function(error) {
  // エラーハンドリングを記述する
}).finally(function() {
  // 成功・失敗に関わらず最後に実行する処理を記述する(ES2021から追加されたもの)
});
みつた
このようにPromiseオブジェクトにはthencatchfinallyといったメソッドがあります。
なやむくん
まだちょっとよく分かりません…。
みつた
OKです。それでは1つ1つのコードを確認していきましょう!

new Promise(function(resolve, reject) { ... })

new Promise(function(resolve, reject) { ... })の中には記述された非同期処理を実行します。

関数の引数にはresolve 関数と reject 関数が渡されています。

resolve 関数には成功時の値を、reject 関数には失敗時の値を渡します。

.then(function(result) { ... })

.then(function(result) { ... })の中には、Promiseの処理が成功した場合に実行される内容を記述します。そのため成功時の結果はresolve 関数に渡ります。

.catch(function(error) { ... })

.catch(function(error) { ... })の中には、Promiseの処理が失敗した場合に実行される内容を記述します。そのため失敗時の結果はreject 関数に渡ります。

.finally(function() { ... })

.finally(function() { ... })の中には、Promise処理の成功・失敗に関わらず最後に実行する処理をします。

使用例

上記の説明を踏まえてPromiseの使用例を紹介します。

以下の例ではランダムな値を生成し、その値が50以上なら「成功」、未満であれば「失敗」とした非同期処理を実行しています。

const getRandomNumberAsync = () => {
  return new Promise(function (resolve, reject) {
    setTimeout(() => {
      const randomNum = Math.floor(Math.random() * 100);
      if (randomNum >= 50) {
        // ランダムに生成した数値が50以上か、未満かで処理内容を分ける
        resolve(randomNum);
      } else {
        reject(randomNum);
      }
    }, 3000);
  });
};
getRandomNumberAsync()
.then((result) => {
console.log("Success:", result); // 実行結果が「成功」の場合はこちらの処理が実行される
})
.catch((error) => {
console.error("Error:", error); // 実行結果が「失敗」の場合はこちらの処理が実行される
})
.finally(() => {
console.log("非同期処理を終了します。"); // 成功・失敗に関わらず最後に実行される
});

getRandomNumberAsync関数では、ランダムに生成した数値が50以上であれば「成功」として、未満であれば「失敗」という処理を3秒後に実行させます。

みつた
非同期処理の流れをわかりやすくsetTimeoutを利用していますが、setTimeoutを利用しなくても、非同期処理の実行が終わるまでは以降の処理は実行されません。

50以上の数値であれば成功とみなされresolveにその値が渡り、thenメソッドが実行されます。

また50未満であれば失敗とみなされrejectに値が渡り、catchメソッドが実行されます。

みつた
resolveに渡った値をresultに、rejectに渡った値をerrorに渡しています。

最後の処理のfinallyメソッドは成功・失敗に関わらず実行されるのでどちらの結果でも処理が実行されます。

処理結果は以下の通りになるはずです。

// 実行結果

// 成功の場合
Success: 70
非同期処理を終了します。

// 失敗の場合
Error: 8
(anonymous)
Promise.catch (async)
(anonymous)
script.js:22 非同期処理を終了します。

asyncとawait

Promiseをより簡潔に扱うことができるのがこのasyncawaitです。

async

asyncは関数名の前に付けることができます。asyncを指定することで非同期関数となりPromiseを返します。

async function getRandomNumberAsync() {}
みつた
返却する値がPromiseであるので、thencatchfinallyプロパティが使用できます。

await

await非同期関数の処理が終了するのを制御することができる記述です。

つまりawaitを記述した処理は、非同期処理が終わるのを待つことができるということです。

awaitasync関数内で記述しないとエラーになりますのでセットで使用するように覚えておきましょう。

awite getRandomNumberAsync()

使用例

先程のPromiseで使用例を紹介しましたが、そのコードでasyncawaitを使った書き方に変更しました。

// 非同期処理の内容は変更なし
const getRandomNumberAsync = () => {
  return new Promise(function (resolve, reject) {
    setTimeout(() => {
      const randomNum = Math.floor(Math.random() * 100);
      if (randomNum >= 50) {
        resolve(randomNum);
      } else {
        reject(randomNum);
      }
    }, 3000);
  });
};

// 非同期処理の結果の表示方法が変更
async function getRandomNumberAsync() {
  try {
    const result = await getRandomNumberAsync();
    console.log("Success:", result);
  } catch (err) {
    console.error("Error:", err);
  } finally {
    console.log("非同期処理を終了します。");
  }
}
executeAsync();
みつた
非同期処理の内容ではなくて、処理結果を表示するコードが変更になっています。
なやむさん
thenメソッドではなくて、tryが出てきました…。あと書き方も微妙に違う。

まずasync function getRandomNumberAsync() {}非同期関数であることを指定しています。

asyncを付けることで処理内でawaitを使用することができるようになります。

処理内にはconst result = await getRandomNumberAsync();とあります。

これはgetRandomNumberAsync関数の処理が実行され終わるまで待つ形になるので、非同期的に制御ができるようになります。

例外処理

Promiseの解説ではthencatchfinallyメソッドを使用して成功時の処理、失敗時の処理を設定していました。

今回asyncawaitを使用した処理の中では、例外処理と言われる構文を使用しました。

なやむくん
例外処理?初めて聞きました…。
みつた
分かりやすく説明しますのでご安心を!

例外処理とはtry…catch…finallyで構成される構文で、try内の処理で例外が発生した場合、その時点でtryでの処理は中断されcatch内の処理に移行することができます。

先程紹介したコードを今一度確認してみましょう。

async function getRandomNumberAsync() {
  try {
    const result = await getRandomNumberAsync();
    console.log("Success:", result);
  } catch (err) {
    console.error("Error:", err);
  } finally {
    console.log("非同期処理を終了します。");
  }
}
executeAsync();

例えばtryの中ではawait getRandomNumberAsync();とあります。

これはgetRandomNumberAsync関数の処理の終わりを待って、以降の処理を実行していくという処理ですが、この関数で何かエラーだったり例外があったとします。

そのときにこの処理の後にconsole.log("Success:", result);の成功した場合の処理がありますが、この処理を実行する前catch内の処理に移行することができます。

catch内の処理にはエラー(例外)があった場合の処理を記述します。

finallyの役割は先程説明したthencatchfinallyメソッドのfinallyメソッドと同じ意味でtrycatchの後に必ず実行されるものです。

みつた
finallyは省略することが可能です。

throw文

意図的にcatchの処理に移行させることもできます。それがthrow 文です。

throw文を使用することで、処理が実行された瞬間処理を中断しcatchの処理に移行します。

async function executeAsync() {
  try {
    const result = await getRandomNumberAsync();
    throw new Error("処理が中断されました。catchに移行します。"); // thorow文
    console.log("Success:", result); // この処理は実行されません
  } catch (err) {
    console.log(err);
  } finally {
    console.log("非同期処理を終了します。");
  }
}
executeAsync();
みつた
実際に例外が発生したときにエラーハンドリングが正常に行われるか確認する際に使用されますね!

まとめ

ここまで読んでいただきありがとうございました。

非同期処理のまとめです。

ポイント

  • 同期処理は、処理を1つずつ順番に実行していく
  • 非同期処理は、1つの処理が終了するのを待つことなく別の処理を実行できる
  • JavaScriptはシングルスレッドである
  • setTimeoutは、処理にタイマーを設定し非同期処理を行うことができる
  • Promiseは、非同期処理を行う事ができるオブジェクト
  • Promiseは、thencatchfinallyメソッドを使用して、成功、失敗時の処理を出し分ける
  • asyncを指定することで非同期関数となりPromiseを返す
  • await非同期関数の処理が終了するのを制御することができる
  • 例外処理とはtry…catch…finallyで構成される構文で、try内の処理で例外が発生した場合、その時点でtryでの処理は中断されcatch内の処理に移行する
  • throw文を使用することで、処理が実行された瞬間処理を中断しcatchの処理に移行できる
  • この記事を書いた人
  • 最新記事

みつた

完全未経験&異業種から30歳の年でIT企業に転職。

Web系開発言語が好き。
どちらかというとバックエンドよりもフロントエンドが好き(現時点では…)

最近はサウナと観葉植物にハマっている。
野球が好きで一応投手。

-JavaScript