【実行例】javascriptで見かけるasync/awaitの役割と実装の仕方
今回はjavascriptにおいてasyncとawaitの役割と使い方を簡単に解説させていただきます。
javascriptでは同期処理と非同期処理の使い分けが存在するため、プログラムが意図した順番に実行されない場合があります。
私もこのasyncとawaitの仕様に慣れてきたものの、いまだに浅学非才の身であります。
手前味噌ではありますが、同期処理と非同期処理を実感できる簡単なプログラムも書いてきましたので、是非ご覧ください。
同期処理と非同期処理について
同期処理と非同期処理の説明として、以下のようなタスクを例に取り上げます。
- 洗濯機を回す
- 晩ご飯を食べる
- 洗濯物を干す
上の例で挙げたものは日常生活で我々が普段から行っているものですが、これらのタスクを同期処理と非同期処理それぞれの手法で行った場合の処理の流れについて以下に示します。
同期処理
同期処理は並べられたタスクの順番に沿って1つづつ実行し、タスクが完了したら次のタスクへ進むという単純な処理を行う方法です。
つまり、以下の図のような時系列でタスクが行われます。
このようにタスクを一つ一つ順番に行って行く処理方法です。
メリット、デメリットとしては以下のようなものがあります。
- メリット
- タスク処理の時系列が分かりやすい
- タスクの実行順序を管理しやすい
- デメリット
- 全てのタスク完了まで時間がかかる
- リソースの有効活用ができない
非同期処理
続いて非同期処理についてです。
これは分かりやすく言うと、何かタスクをこなしている間でも、余裕があればほかのタスクも並行して進めてしまうという処理方法です。
先ほどと同様に、「洗濯機を回す」「晩ご飯を食べる」「洗濯物を干す」というタスクを例に非同期処理によるタスク処理を下の図に示します。
非同期処理では上の例のように「洗濯機を回す」間に「晩ご飯を食べる」というタスクをこなすことで、全体的な作業時間を減らしています。
同期処理よりもタスク処理の順序が複雑になりますが、タスク管理を正しく行うことで、効率よく全体の作業を進めることができます。
非同期処理のメリット、デメリットとしては以下のようなものがあげられます。
- メリット
- 複数のタスク処理時間を短縮できる
- リソースの有効活用ができる
- デメリット
- タスクの実行順序が分かりにくい
- タスク実行順序の管理ができないと予期しないエラーが発生する
JavaScriptによる実行例
JavaScriptは他のプログラミング言語のほとんどがそうであるように、1行目から順に実行していく同期処理システムです。
しかし、その処理を待たずに次々と処理を進めていくので非同期処理をしているとも言えます。
以上の例を踏まえて、以下に簡単な同期処理と非同期処理の実装の仕方と、それぞれの処理手法による実行結果の違いが現れる例を示します。
非同期処理で起こるエラーについて
以下のプログラムは、JavaScriptで書かれています。
fetch関数で任意のURL先の情報を取得し、h1タグ内の文字列を取得してコンソールに出力するというプログラムです。
関数の最初と最後にそれぞれ「最初です。」「最後です。」と出力するようにしています。
/*よさそうに見えてエラーになるプログラム*/
function test(){
console.log("最初です。");
const a = fetch('https://***'); /*fetch関数でURL先の情報を取得*/
const b = a.text(); /*fetch関数が完了する前に実行される→エラー発生*/
var m = b.match(<h1>(.*)<\/h1>/);/*h1タグ内の文字列を取得*/
console.log(m[1]);
console.log("最後です。");
}
test();/*関数の実行*/
一見は何も問題なさそうなプログラムですが、7行目のtext()関数を実行するところでエラーが出てしまいます。
以下は実際のエラーの様子です。
上の図では「a」という変数に適切な値が設定されていないために、「Uncaught TypeError」が出力されています。
つまり、fetch関数の完了を待たずに次の行のソースコードが実行されてしまったということが考えられます。
この6行目と7行目は実行順序が決まっているため、非同期処理では処理できない例の一つとなります。
非同期処理では、たまにこのようなタスクの不適切な実行順序による予期せぬエラーが発生します。
以下では、このプログラムの6行目と行目に同期処理をさせることで正しくプログラム全体が実行されるように修正してみます。
async/awaitで同期処理のように扱う
以下のJavaScriptプログラムは、先ほど記述したエラー文を出力するプログラムの適切な場所に「async」と「await」を記述したものです。
「async」と「await」によって適切にタスクの実行順序を管理しています。
/*asyncを挿入*/
async function test(){
console.log("最初です。");
/*awaitを挿入*/
const a = await fetch('https://***'); /*fetch関数でURL先の情報を取得*/
/*awaitを挿入*/
const b = await a.text(); /*awaitによりfetch関数の完了後に実行される*/
var m = b.match(<h1>(.*)<\/h1>/); /*h1タグ内の文字列を取得*/
console.log(m[1]);
console.log("最後です。");
}
test(); /*関数の実行*/
「await」は「async」をつけた関数の中で使えるので、「async/await」とセットで紹介されることが多いです。
上記のように「await」がついた処理については実行が完了するまで待機させることができます。
つまり、上の例に関して言えば、6行目のfetch関数をawaitにより実行完了まで待機させることで、変数「a」に確実に値が代入された状態で7行目の処理を行わせることができます。
このJavaScriptを実行した結果は以下の図のようになります。
awaitによって一時的に同期処理を行わせることで、適切な順序で正しくプログラム全体が実行されました。
おまけ(Promiseチェーンによる実行)
最後に「Promiseチェーン」というもう一つのタスク処理手法をご紹介させていただきます。
以下にさきほどまで述べたJavaScriptプログラムと同じ挙動を示すプログラムを「Promiseチェーン」を使って書いたものです。
/*Promiseチェーンによる同期処理*/
function test(){
console.log("最初です。");
fetch('https://***') /*fetch関数でURL先の情報を取得*/
.then(a => { /*fetch関数で取得した値がaへ渡されます*/
return a.text();
})
.then(b => { /*上のthenで取得した値がbへ渡されます*/
var m = b.match(/<\h1>(.*)<\/h1>/);
console.log(m[1]);
console.log("最後です。");
})
};
test();
ここでのポイントはthenで囲われたタスクは1つのタスクとして同期処理的に扱われます。
またthenメソッドはfetch関数のようなPromiseオブジェクトに対して使用できます。
thenメソッドは受け取るPromiseの値が実行完了(fulfilled)か拒否(rejected)を受け取るまでします。
また、thenメソッド自身もPromiseを返すため、thenを連続してつなげることで、複数のタスクを同期処理的に扱うことができます。
実はこのthenを使用したPromiseチェーンという概念はasync/awaitが実装される以前から存在していました。
Promiseをより扱いやすくしようと生み出されたものがasync/awaitですので、両者の間には深いかかわりがあります。
気になった方は是非実際に上のプログラムを参考に実行してみてください。
参考記事
TOPへ戻る関連記事