JavaScript:非同期(ネットワーク無しでも理解する)
音声
目次
非同期とは?
非同期(ひどうき)とは、「待ちが発生する処理を始めても、そこで止まらずに先へ進める」ことです。 たとえば「1秒待つ」「読み込みを待つ」のような処理は、終わるまで待っていると画面が固まってしまいます。 そこでJavaScriptは、待ちが必要な処理はいったん予約して、他の処理を先に進められるようにしています。
このページではネットワーク通信は使わず、setTimeout(あとで1回実行)と
setInterval(一定間隔で繰り返し)を使って「予約 → 後から実行される」感覚を体験します。
その上で、Promise / then-catch / async-await によって「後から返ってくる結果」を
きれいに扱う方法を学びます。
デモ(このページでそのまま動きます)
setTimeout/setIntervalで非同期を体感
非同期は「いま実行せず、あとで実行する予約」です。
setTimeout は「1回だけ遅らせる予約」、setInterval は「一定間隔で繰り返す予約」です。
まずは ログの順番 に注目してください。ボタンを押した直後のログと、あとから出るログが混ざったら「非同期っぽさ」を掴めています。
Promiseの基本(then/catch)
Promiseは「将来の結果(成功 or 失敗)を表す箱」です。
then は成功した結果を受け取り、catch は失敗(reject)を受け取ります。
この小項目では「あとから届く結果を、後処理としてつなぐ」感覚を掴みます。
async/awaitの書き方
async/await は Promise を「上から順に読む」書き方にできます(同期っぽく見える)。
await は「ここで結果が出るまで待つ」ように見えますが、UI全体を止めるわけではありません。
thenチェーンが長くなると読みづらいので、順番が大事な処理は await が読みやすいです。
例外処理(try/catch)との組み合わせ
非同期の失敗は放置すると「どこで失敗したか」が見えづらくなります。
await する処理を try/catch で包むと、失敗理由をその場で拾えてデバッグが楽です。
この小項目では「reject(失敗)を catch で受け取る」流れを確認します。
並列実行(Promise.all)の考え方(軽く)
独立した非同期処理を同時に始めて全て終わるのを待つときにPromise.allを使います。個別にawaitするより高速になることが多く、結果は配列で返ります。要チェックとして、1つでもrejectすると全体がrejectになります。
最小コード:
<!-- HTML欄:CodePenのHTMLに貼ります -->
<button id="btn1">setTimeout/Interval</button>
<button id="btn2">Promise then/catch</button>
<button id="btn3">async/await</button>
<button id="btn4">try/catchでエラー</button>
<button id="btn5">Promise.all</button>
<pre id="out"></pre>
// JS欄
const out = document.getElementById('out'); // 画面にログを出す場所
const log = m => { // 観察用:Console + 画面に同じログを出す
console.log(m);
out.textContent += m + '\n';
};
function wait(ms){
// 「ms ミリ秒たったら成功する Promise」を返す(= 待ちをPromise化)
return new Promise(res => setTimeout(() => res(ms), ms));
}
document.getElementById('btn1').onclick = () => {
log('setTimeout開始'); // これはすぐ出る(同期的に実行)
// 1秒後に実行を「予約」するだけ(今は止まらない)
setTimeout(() => log('1秒後'), 1000);
// 0.3秒ごとに繰り返し実行を予約(止めない限り tick が増える)
const id = setInterval(() => log('tick'), 300);
// 1.1秒後に interval を止める(clearIntervalしないと永遠に動く)
setTimeout(() => { clearInterval(id); log('interval停止'); }, 1100);
};
document.getElementById('btn2').onclick = () => {
// wait(700) は「700ms後に成功するPromise」
// then:成功したときに後から実行される/catch:失敗したとき
wait(700)
.then(ms => log(ms + 'ms完了 then'))
.catch(e => log('thenエラー:' + e));
};
document.getElementById('btn3').onclick = async () => {
log('async開始'); // これはすぐ出る
// await:Promiseの完了を「待つ」ように見えるが、UI/他の処理は止めない
const v = await wait(500);
log('awaitで待った:' + v + 'ms'); // 完了後に続きが実行される
};
document.getElementById('btn4').onclick = async () => {
try{
// 400ms後にわざと失敗(reject)させるPromise
await new Promise((_, rej) =>
setTimeout(() => rej(new Error('失敗')), 400)
);
}catch(e){
// await中の失敗は try/catch で受け取れる(非同期でも理由が追える)
log('catchで受けた:' + e.message);
}
};
document.getElementById('btn5').onclick = () => {
// Promise.all:複数のPromiseを「同時に開始」して、全部終わったら then が1回だけ動く
Promise.all([wait(300), wait(600)])
.then(a => log('Promise.all完了:' + a.join(',')));
};
貼り付け場所は、CodePenならHTML欄にHTMLを、JS欄にJSを貼ります。ローカルでは<body>内にHTMLを置き、<script>でJSを読み込む形にします。
実行方法は、ページを開いて各ボタンを押すと#outとConsoleにログが出ます。期待どおりのタイミングやエラー表示が確認できればOKです。
確認方法は、#outに順にログが出ることと、Consoleにも同じログが出ることを見ます。
よくあるミスは、HTMLの要素IDとJSの参照を合わせ忘れることです。その場合はonclickが未定義になりやすいので、DOMContentLoadedで初期化するかIDを一致させます。
使い分けの基準は、順次処理はasync/awaitが読みやすく、短いチェーンはthen/catchでもよいです。独立処理の同時実行はPromise.allを使い、繰り返しは状況に応じてsetIntervalか逐次setTimeoutを選びます。
注意(ここだけ)
setTimeoutの順序は「待ち時間が短い順に即実行」ではなく、キューの都合で前後します。ログで順序を確認しながら学ぶのが安全です。async/awaitでも例外は起きます。非同期処理はtry/catchで包まないと「落ちた理由」が見えづらくなります。並列(
Promise.all)は一つでも失敗すると全体が失敗扱いになります。必要なら個別に失敗を吸収する設計にします。
要約
- setTimeoutは遅延実行で、setIntervalは繰り返し実行ですが、どちらもUIをブロックしません。
- Promiseは非同期の結果を表し、then/catchで処理を繋げます。
- async/awaitでPromiseを同期風に書けるため、可読性が上がります。
- awaitとtry/catchを組み合わせると、自然な例外処理ができます。
- Promise.allで並列実行して効率化できますが、1つの失敗で全体が失敗します。