async / awaitについて、再確認(超初心者向け)
はじめに
カレーを作るとき、ご飯を炊く作業と野菜を切る作業は同時に進められますが、ご飯をお皿によそうのは、ご飯が炊き上がるまで待たなければできません。このような状況をコンピューターの世界で考えると、ご飯を炊きながら野菜を切るのは「非同期処理」、ご飯が炊き上がるまで待つのは「同期処理」と呼ばれます。そして、この同期・非同期の動作をプログラム上で指示するために使われるのが、async / await という構文です。
この記事では、この async / await をわかりやすく解説します。
async / await は、プログラムの中で「一度にすべて処理せず、完了するのを待ちながら進める」仕組みを簡単に書けるようにするための方法です。たとえば、インターネットから情報を取得する場合、データが返ってくるまで時間がかかることがあります。その間、プログラムを停止させるのではなく、他の作業を並行して進めることができるのです。
ここでは、JavaScript での async
/ await
の使い方や注意するポイントを説明します。
基本的な使い方
-
async
とは?
async
を関数の前に付けることで、その関数は非同期であることを示します。
関数(プログラムのまとまり)の前に async
を付けると、「この関数の中には、終わるのを待たなければいけない作業があるかも」とプログラムに教えることができます。
async
の例
async function getMessage() {
return "こんにちは!";
}
相手に挨拶をして、返事を画面に表示させるアプリを作ります。この関数は「こんにちは!」と返すのですが、返事がすぐに返ってこない場合を想定して(すぐに挨拶してくれない人もいるので)非同期として扱っています。
-
await
とは?
await
を使うと、「この作業が終わるのを待つよ」という指示が出せます。await
は必ずasync
関数の中で使用する必要があります。
await
の例
async function showMessage() {
const message = await getMessage(); // getMessageが終わるのを待つ
console.log(message); // 結果を表示する
}
showMessage();
// 出力: こんにちは!
このプログラムでは、まず getMessage() が終了するのを待ち、その結果を message に代入します。その後、その結果を画面に表示します。
これは文字を返すだけの簡単な例ですが、たとえば、メッセージを取って来るためにネットワークに問い合わせを行う場合、返事が届くまでに数秒かかることがあります。await を付けると、返事が届くのを待ってから値を受け取ることができますが、await を付けない場合は、メッセージを受け取る前にテキストではない値が画面に出力されてしまいます。
エラーが起きたらどうする?
プログラムを実行するとにエラーが起きることもあります。たとえば、インターネットが繋がらないときです。その場合は try
(やってみる)と catch
(失敗したらこうする)を使います。
-
fetch()
関数について
fetch は、例えばインターネットを経由して相手からのメッセージを取得するときに使います。
jsonplaceholder.typicode.com はメッセージの代わりにレスポンスを返すテストサイトです。
エラーを拾い上げる例
async function fetchData() {
try {
const response = await fetch("https://jsonplaceholder.typicode.com/posts/1");
const data = await response.json();
console.log(data);
} catch (error) {
console.error("返事を受け取れませんでした:", error);
}
}
fetchData();
このように書くと、エラーでアプリが止まることを防ぐことができます。
async
/ await
を使うときの注意点
JavaScript で非同期処理を使うとき、async
は非常に便利です。でも、正しく使わないと、予想外の動きをしてしまうことがあります。
await
を使うと順番に動いてくれます。ただ、何にでも await
を使えば良いわけではなく、使い方は以下のように考えましょう。
方法 | よい | わるい |
---|---|---|
awaitを使う | 順番をきっちり守って動く | 一個ずつ動くから遅く感じることがある |
awaitを使わない | いろんなことを一気にできる | 順番がバラバラになる |
await
を使うと RPGのようなゲームでキャラクターが順番に行動するみたいなイメージです。
一方 await
を使わないのは、フォートナイトのようにみんなが同時に動いてワイワイしている感じです。でも、順番を決められないから困ることもありますよね。
await
をつけないとどうなる?
await
をつけないで async
関数を呼び出すと、関数の中の処理が「バックグラウンドで実行」されます。そのため、プログラムの処理順序が変わることがあります。
例えば、次のコードを見てみましょう。
- メイン関数(
main()
)が非同期でサブ関数(fetchData()
)を実行します。
// 非同期で、web読込を行う関数(読み込み、JSON化する)
const fetchData = async (id) => {
console.log("データを取得中..."); // タイトルを表示
const response = await fetch("https://jsonplaceholder.typicode.com/posts/" + id);
const data = await response.json();
console.log(data);
console.log("データを取得完了");
return;
};
// 非同期で実行するメイン関数
const main = async () => {
console.log("[main 開始]"); // メイン関数の開始表示
// データを取得
fetchData(1);
// データを取得
fetchData(2);
console.log("[main 終了]"); // メイン関数の終了表示
};
// mainを実行。呼び先で非同期にサブ関数を呼ぶ。
main();
このコードの実行結果はたまたま次のようになりました。id が 2 -> 1 の順になっています。これは、fetchData が非同期に実行され、相手のサーバがレスポンスを返す順に処理が終わるので、順序が保証されないためです。
% node sample_load_async.js
[main 開始]
データを取得中...
データを取得中...
[main 終了]
{
userId: 1,
id: 2,
title: 'qui est esse',
body: 'est rerum tempore vitae\n' +
'sequi sint nihil reprehenderit dolor beatae ea dolores neque\n' +
'fugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\n' +
'qui aperiam non debitis possimus qui neque nisi nulla'
}
データを取得完了
{
userId: 1,
id: 1,
title: 'sunt aut facere repellat provident occaecati excepturi optio reprehenderit',
body: 'quia et suscipit\n' +
'suscipit recusandae consequuntur expedita et cum\n' +
'reprehenderit molestiae ut ut quas totam\n' +
'nostrum rerum est autem sunt rem eveniet architecto'
}
データを取得完了
fetchData()
が始まるとすぐに "[main 終了]" が表示されます。これは、await
をつけていないため、fetchData
の実行を待たずに次の行に進んでしまうからです。
更に、id が 2 -> 1 の順になっています。id 順に表示させたい場合、この順番では困りますよね。このように、await
を忘れると処理の順序が予想外になり、問題を引き起こすことがあります。
どうすればいい?
await
をつける。
次のコードを見てみましょう。先ほどと違うのは fetchData()
を呼ぶときに await
をつけているだけです。
// 非同期で、web読込を行う関数(読み込み、JSON化する)
const fetchData = async (id) => {
console.log("データを取得中..."); // タイトルを表示
const response = await fetch("https://jsonplaceholder.typicode.com/posts/" + id);
const data = await response.json();
console.log(data);
console.log("データを取得完了");
return;
};
// 非同期で実行するメイン関数
const main = async () => {
console.log("[main 開始]"); // メイン関数の開始表示
// データを取得
await fetchData(1);
// データを取得
await fetchData(2);
console.log("[main 終了]"); // メイン関数の終了表示
};
// mainを実行。呼び先で非同期にサブ関数を呼ぶ。
main();
このコードの実行結果は次のようになります(%はプロンプトです):
% node sample_load_await.js
[main 開始]
データを取得中...
{
userId: 1,
id: 1,
title: 'sunt aut facere repellat provident occaecati excepturi optio reprehenderit',
body: 'quia et suscipit\n' +
'suscipit recusandae consequuntur expedita et cum\n' +
'reprehenderit molestiae ut ut quas totam\n' +
'nostrum rerum est autem sunt rem eveniet architecto'
}
データを取得完了
データを取得中...
{
userId: 1,
id: 2,
title: 'qui est esse',
body: 'est rerum tempore vitae\n' +
'sequi sint nihil reprehenderit dolor beatae ea dolores neque\n' +
'fugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\n' +
'qui aperiam non debitis possimus qui neque nisi nulla'
}
データを取得完了
[main 終了]
今度は fetchData()
が始まると、 "データを取得中..."、"データを取得完了" と表示され、最後に "[main 終了]" が表示されます。これは、await
をつけているため、fetchData
の実行が終わるのを待っているからです。
何度実行しても期待通り id 順に表示されてますよね。一方で、順番に処理をしているので、時間がかかってしまいます。
まとめ
-
async
関数を使うときは、処理を同期する場合、await
を忘れないようにしましょう。 -
await
を忘れると処理順が入れ替わることがあります。用途に応じて使い分けるようにしましょう。
このように、async
/ await
構文を使えばある程度、非同期、同期を組み合わせた処理を簡潔に書けるようになります。
しかし、関数が増えるとどうでしょう。また、 promise.all や promise.race を駆使するような async
/ await
処理になると、関数の依存関係が複雑になり、かなり難しいコードになります。
そこでGraphAIの出番になります。
GraphAIは宣言型のプログラミングスタイルで非同期処理を簡潔に記述することができます。
GraphAIのイメージ。
GraphAIの記事
人工知能を活用したアプリケーションやサービスを活用し、内発的動機付けで行動するエンジニア、起業家、社会起業家をサポートするコミュニティーです。 singularitysociety.org
Discussion