Promise と await / asyncとは?
▶︎ 同期処理 とは、プログラムを上から下へ順番に行う処理のことです。
▶︎ 非同期処理 とは、プログラム実行中に他のタスクの完了を待たずに行う処理のことです。
JavaScriptは、一度に一つのことしかできない仕組み(シングルスレッド)で動いており、時間がかかる処理(例えば、データの取得やファイルの読み込み)を行う場合は、他の処理が止まらないように非同期で実行する仕組みが用意されています。
なので、下図のように処理Bに時間がかかる場合はBを飛ばして、AとCを 同期的 に、Bを 非同期的 に実行します。
期待する処理の流れが「A → B → C」だとしても、実際は「A → C → B」になってしまうというわけです。
このような処理の流れを、非同期処理を制御して期待通りの「A → B → C」に設定したり、その他の自由な順序で行うことができるのが「Promise」や「async/await」です。
この記事では、非同期処理を行うための 「Promise」と「async / await」 の使い方について初心者向けに解説します。
Promise の使い方
まずは、非同期処理を行う「Promise」の使い方について解説します。
非同期処理は、常に成功する保証はありません。
なので、Promiseを使用して、その処理の「成功」と「失敗」両方の処理を準備する必要があります。
こうすることで、Promiseという名前の通り、非同期処理が(成功、失敗に関わらず)必ず完了することを「約束」します。
Promiseの基本的な構文は以下の通りです。
new Promise((resolve, reject) => {
// ここに非同期処理を記述
if ( 成功条件 ) {
resolve(); // 成功した場合に呼ばれる
} else {
reject(); // 失敗した場合に呼ばれる
}
}).then(() => {
// resolve() が呼ばれた場合の処理
}).catch(() => {
// reject() が呼ばれた場合の処理
});
new Promise() の関数(コンストラクタ)の引数には resolve と reject という関数が渡されます。
Promise内に非同期処理を記述する際は、成功時に resolve() 、失敗時に reject() を呼び出した後、.then()で成功後の処理を、.catch()で失敗後の処理を指定します。
そしてPromiseオブジェクトは、次の3つの状態を持ちます。
- pending(保留): 初期状態。処理がまだ完了していない。
- fulfilled(成功): 処理が成功し、結果が得られた。
- rejected(失敗): 処理が失敗した。
▶︎new Promise() で作成されたPromiseオブジェクトは、最初はpending状態です。
▶︎処理が成功すると、状態はfulfilledに変わり、thenメソッドで指定した処理が実行されます。
▶︎処理が失敗すると、状態はrejectedに変わり、catchメソッドで指定した処理が実行されます。
Promiseを使った例文
console.log( '処理A' );
setTimeout(() => {
console.log('処理B');
}, 1000);
console.log('処理C');
上記の処理は、setTimeout関数で処理に1秒かかる非同期処理の「処理B」を飛ばし、「A → C → B」の順に実行されます。
Promiseを使って「A → B → C」の順番でコンソールに出力する場合は以下のようになります。
(今回は、処理が失敗した場合の reject()と.catch() は省略します。)
console.log( '処理A' );
new Promise((resolve) => {
setTimeout(() => {
console.log('処理B');
resolve();
}, 1000);
}).then(() => {
console.log('処理C');
});
上記のコードでは、「処理A」を実行後、「処理B」(非同期処理)が終わるタイミング(1秒後)にresolve()を呼び出すことで、Promiseを成功(完了)させています。
そして、resolve() が呼ばれることで.then()の中の「処理C」が実行されます。
resolveとrejectの引数に値を渡す
resolve() と reject() は引数に値を渡すことで、それぞれthen()とcatch()内で使用できます。
ここでは、resolve() の引数に「成功したよ!」、reject()に「失敗したよ!」という文字列を渡してみましょう。
new Promise((resolve, reject) => {
setTimeout(() => {
const success = true; // 仮に成功とする
if (success) {
resolve('成功したよ!'); // 成功時の値を渡す
} else {
reject('失敗したよ!'); // 失敗時のエラーメッセージを渡す
}
}, 1000);
}).then((data) => {
console.log(data); // 成功時の値を表示
}).catch((error) => {
console.log(error); // 失敗時のエラーメッセージを表示
});
「const success = true;」の部分で仮に成功としているので、 resolve() に渡した文字列「成功したよ!」が then() に渡って表示されていますね。(引数で data として受け取っていますが、他の文字列でもOK!)
.thenを複数繋ぐ処理(メソッドチェーン)
thenメソッド は、前の then で返された値を、次の then に順番に渡すことができます。
then のコールバック関数の引数でその値を受け取ることで、その値を then の中で使用することができます。
new Promise((resolve, reject) => {
setTimeout(() => {
const success = true; // 仮に成功としています
if (success) {
resolve('最初のデータ'); // 成功時のメッセージを渡す
} else {
reject('エラーが発生しました'); // 失敗時のエラーメッセージを渡す
}
}, 1000);
}).then((data1) => {
console.log(data1); // "最初のデータ"を表示
return '次のデータ';
}).then((data2) => {
console.log(data2); // "次のデータ"を表示
return 'さらに次のデータ';
}).then((data3) => {
console.log(data3); // "さらに次のデータ"を表示
}).catch((error) => {
console.log(error);
});
「data1 → data2 → data3」 と順番に値が渡っているのがわかると思います。
Promise.all (複数のPromiseが全て完了したら処理)
複数のPromiseを並行して実行し、それらすべてが成功(またはいずれかが失敗)してから処理する場合は Promise.all というメソッドを使用します。そして、
Promise.all は、Promiseの配列を受け取り、すべてのPromiseの処理が成功したらその結果を配列で返します。
では早速、3つの処理時間の異なるPromiseを同時に実行して、全て成功した場合を例にpromise.allで処理を実行してみましょう。
const promise1 = new Promise((resolve) => {
setTimeout(() => {
resolve('Promise 1 完了');
}, 1000);
});
const promise2 = new Promise((resolve) => {
setTimeout(() => {
resolve('Promise 2 完了');
}, 2000);
});
const promise3 = new Promise((resolve) => {
setTimeout(() => {
resolve('Promise 3 完了');
}, 3000);
});
Promise.all([promise1, promise2, promise3]).then((results) => {
console.log(results);
})
すべての Promiseが成功したので、then メソッドが呼ばれ、その引数として各Promiseの結果が配列で渡されていますね。
Promise.race (複数のPromiseがどれか一つ完了したら処理)
複数の並行して実行されたPromiseの処理の内、最初に完了したPromiseの結果やエラーを返す場合は、Promise.race を使用します。
Promise.race の使用例としては、タイムアウトを設定して一定時間内に応答が得られなかった場合の処理や、複数のデータソースやサーバーからの応答を待つ場合に、最初に応答を返したものだけを使用することができます。
では早速、3つの処理時間の異なるPromiseを同時に実行して、最初に実行されたpromiseだけを処理してみましょう。
const promise1 = new Promise((resolve) => {
setTimeout(() => {
resolve('Promise 1 完了');
}, 1000);
});
const promise2 = new Promise((resolve) => {
setTimeout(() => {
resolve('Promise 2 完了');
}, 500);
});
const promise3 = new Promise((resolve) => {
setTimeout(() => {
resolve('Promise 3 完了');
}, 2000);
});
Promise.race([promise1, promise2, promise3]).then((results) => {
console.log(results); // "Promise 2 完了"が表示される
});
渡されたPromiseの中で最も早く完了したもの(今回はPromise2)が then に渡されますね。
async / await の使い方
async / await は JavaScript で非同期処理をより簡潔に書くための構文です。
Promise を使った非同期処理と比べてコードが読みやすくなり、エラー処理も簡単になります。
以下は、処理をA→B→Cの順に実行する通常のPromiseの処理です。 (エラー処理を省略しています。)
console.log( '処理A' );
new Promise((resolve) => {
setTimeout(() => {
console.log('処理B');
resolve();
}, 1000);
}).then(() => {
console.log('処理C');
});
以上の処理をasync / await で書き換えると次のようになります。
async function execute() {
console.log('処理A');
await new Promise((resolve) => {
setTimeout(() => {
console.log('処理B');
resolve();
}, 1000);
});
console.log('処理C');
}
execute();
これで、Promiseと同じ処理をasync / await を使用して簡潔に書くことができました。
async / await の記述のポイントは以下の通りです。
- new Promise の前に await を追記することで、このPromiseの処理を待つことができます。
これにより、.then()のようなコールバックが不要になります。 - await は async 関数内でしか使用できないので、処理をasync 関数で囲みます。
エラー処理を行う try catch
今度は、処理の途中でエラーを発生させてみましょう。
エラーは、try / catchを使用することでキャッチして処理することができます。
- tryブロック: エラーが発生する可能性があるコードを囲みます。
- catchブロック : tryブロック内でエラー発生時にエラーをキャッチして処理を行います。
それでは、try / catchを使って、先ほどの処理ABCの内「処理B」についてエラーを発生させてみましょう。
async function execute() {
console.log('処理A');
try {
await new Promise((resolve, reject) => {
setTimeout(() => {
console.log('処理B');
reject('エラー発生!'); // エラーを発生させる
}, 1000);
});
} catch (error) {
console.log(error); // ここでエラーをキャッチ
return; // エラー時、ここで実行を終了する
}
console.log('処理C'); // ここには到達しない
}
execute();
await の処理をtryブロックで囲むことで、エラーをキャッチして処理できていますね。
今回は、catchブロック内でエラーがキャッチされるので、 return文により関数の実行を終了させています。
async / await によるシンプルな非同期処理
Promiseで行っていたthenメソッドによるメソッドチェーンは、async / await を使うことで、thenを使わなくても非同期処理を直線的に書くことができ、処理は上から順に実行されます。
async function fetchData() {
try {
const data1 = await new Promise((resolve, reject) => {
setTimeout(() => {
const success = true; // 仮に成功としています
if (success) {
resolve('最初のデータ'); // 成功時のメッセージを渡す
} else {
reject('エラーが発生しました'); // 失敗時のエラーメッセージを渡す
}}, 1000);
});
console.log(data1); // "最初のデータ"を表示
const data2 = '次のデータ';
console.log(data2); // "次のデータ"を表示
const data3 = 'さらに次のデータ';
console.log(data3); // "さらに次のデータ"を表示
} catch (error) {
console.log(error); // エラーハンドリング
}}
fetchData();
今回は、仮に成功として処理しましたが、順番に処理が実行されていますね。
async / await を使用することで、非同期処理を直線的に書くことができ、上から順に処理が実行されることが確認できます。
最後に
Promisesと async / await は、JavaScriptの非同期処理を扱うための強力なツールです。
Promiseチェーンを使うと、複雑な非同期操作を整理できます。
一方、async / await を使うことで、コードを直線的に書けるため、可読性が向上します。
非同期処理の理解は、モダンなWeb開発に不可欠です。これらの技術を活用し、効率的でエラーに強いアプリケーションを構築しましょう。
Javascript
javascriptの基本的な使い方について解説した記事一覧ページです。
イラストを交えてやさしく解説していますので、ぜひご覧ください。続きを見る