LoginSignup
31
22

More than 3 years have passed since last update.

【JavaScript】非同期処理とasync/await ~難しいこと抜きで、まず使いはじめるための知識~ ( ´ε` )💻

Last updated at Posted at 2021-01-17

この記事について

この記事は、非同期処理のことをほとんど知らなかった方や、
async/awaitを使ったコードをとりあえず書いてみて覚えたいという方のために、
使用上問題ないレベルで理解してもらえるよう、カンタンに説明させていただきたいと思います。

コードを読むときに「何をしたいのかわかる」
コードを書くときに「とりあえず使ってみる」
程度のレベルまで理解していただければOKという、ゆるい説明になります。
正しい知識を追求したい場合は、各自で行っていただければと思います。
(そして共有してください♡)

ちなみに、async/awaitを使わない非同期処理の書き方については、あまり知見がありません。
(自分がプログラミングを学び始める時点で、すでにasync/awaitが誕生していたため。)
予めご承知おきください。

非同期処理とは「上から順番に実行されるわけではない処理」のことです。

JavaScriptのコードは基本的に上から順番に実行されます。
しかしJavaScriptの中には一部、このルールを無視する処理が存在します!
この「上から順番に実行されるわけではない処理」を「非同期処理」と呼びます。

非同期処理を行う関数は、主に2種類あります。

JavaScriptで非同期な処理を行う関数は、主に以下の2種類です。

  1. タイマー処理setTimeout()setInterval()など。)
  2. HTTP通信を行う処理 …つまりAjaxfetch()$ajax()axios()など。)

「HTTP通信を行う」というイメージがよくわからない方への説明

JavaScriptには、
他のURLにアクセスし、そのURLが配信しているデータを取得してくるための関数があります。
これを技術的に言うと、JavaScriptでHTTP通信を行うための関数ということになります。
(JavaScript上でAPIを実行する時などに必要になってくる機能です。)
そして、このJavaScriptでHTTP通信を行う処理のことを、Ajaxと呼びます。

非同期の処理は、処理の完了を待たずに、次の処理の実行が開始されます。
(なんのためにそんなことをするかというと、この処理の実行中に、他の処理も全て止まってしまうような事態を防止するためかと思います!)

(例)
/* 通常の処理は、前の処理の実行が完了してから、次の処理が実行される。 */

console.log(1); 

console.log(2);
// ↑ consoleに 1 と表示されてから実行される。

setTimeout( function(){console.log(3)}, 0 );
// ↑ consoleに 2 と表示されてから実行される。

console.log(4);
// ↑ 前の処理が非同期なので、consoleに 3 と表示されているか否かに関わらず、実行される。

async/awaitは、非同期処理の完了を待つための方法です。

上述の処理は非同期処理であると言いましたが、
setTimeout()はともかく、HTTP通信を行う処理の場合、
返り値が返ってくるまえに次の処理が実行されちゃったら、意味がありません!

そのため、非同期処理の完了を待ってから処理を続行するための方法が生まれました。
その方法の中で、最も新しい方法がasync/awaitなのです。

つまり「非同期処理を、通常の同期処理のように実行させる仕組み」ということです。

async/awaitが現れる前から、そのためのやり方は存在していました。

非同期処理を通常の同期処理のように実行させる仕組みには歴史があり、下記のように変遷してきました。

(過去) コールバック処理Promiseasync/await (現在)

ざっくり説明すると、

コールバック処理

関数の引数に関数を渡すことで、
「最初の処理がおわったら、次の処理を実行するよ」と指定する方法です。
(最近では主にタイマー関数のみで使われる方法かと思います。)

setTimeout( 次の処理, 100 );

Promise

「処理が完了したかどうか」を判別することができる型のことです。
Promiseというのはクラスの名前ですので、利用する際はインスタンスを生成します。

const インスタンス = new Promise( 本来実行したい関数 );

Promiseインスタンスは、関数と同じようにインスタンス()と実行すると、
本来実行したい関数の実行が開始されます。
そして、返り値には「処理が完了したかどうか」を判別できるデータが返されます。
本来実行したい関数の中でreturnされていた値は、そのデータの内部に格納されます。)

このデータのおかげで、JavaScriptは非同期処理を柔軟に扱えるようになりました。
この記事では今後、このデータの事を Promise と呼びます。

Promise に対してメソッドチェーンを使っていくことで、完了後に行う処理を指定できるという仕組みですが、この記事では解説しません。)

async/await

Promise をそのまま扱うよりも、もっとシンプルにコードが書けるようにasync/awaitが登場しました。
この書き方が登場したことにより、今まで以上にシンプルに非同期処理を扱うことができるようになりました。

なのでasync/awaitは、完了状態を判別するための新しい機能というわけではなく、
Promise を使う処理は書き方がめんどくさかったから、もっと簡単に書けるようにした」
というようなものです。
完了状態を判断する際には、結局 Promise (=「処理が完了したかどうか」を判別できるデータ)を利用しています。

async/awaitの使い方

asyncawait
この2つのうち、主役となるのはawaitですので、awaitの使い方から説明していきます。

今後の説明では、非同期処理を行う関数の存在が必要となるので、
例として、非同期処理を行う API() という関数があるものとします。


補足

通常は、非同期処理を行う関数は、引数がもうすこし複雑な記述になります。

Ajax関数の場合
たとえば、Ajaxを実行するためのfetch $ajax axios
または 他社のAPIを実行するために作成されているオリジナルの関数の場合、
引数に 接続先の情報や、接続先に渡したい情報などを含んだオブジェクト を渡すような書き方になります。
タイマー関数の場合
また、Vue.js の this.$nextTick 等をはじめとする タイマー関数 の場合、
引数に 時間経過後に実行したい処理(関数) を渡すような書き方になります。

今回使用する API() は、
それらの情報を既に内部で定義してあるラッパー関数 だと想定していただければ幸いです。
(通常のコードを記述する場合も、
 今回のようにラッパー関数を作成しておくか、
 もしくは引数に渡す情報を前もって変数に定義するなりして、
 すっきりしたコードで記述することをオススメします。)


awaitの使い方

await演算子は、非同期処理の関数を実行するときに使用します。
await演算子を付加して実行された関数は、
処理がおわるまで次に進まないよう、待っていてくれます。

(例)
await API();
// ↑ await を付けない場合、処理が完了するより先に下の行の処理が実行される。

console.log('処理が完了しました。');
// ↑ await を付けたため、 API() の処理がおわってから実行される。

asyncの使い方

async装飾子は、関数を定義するときに使用します。
async装飾子を付けて定義された関数を、便宜上 async関数 と呼びます。)

じつはawait演算子を使用できるのは async関数 の中だけなのです。
なので、awaitを利用するために仕方なく使う という認識で問題ありません。
(グローバルスコープでawaitを使えるようにする案も、検討されているとかいないとか…。)

function文
async function async関数(){
  // await を使う処理は、この中にしか書けない。
}
アロー関数
const async関数 = async ()=>{
  // await を使う処理は、この中にしか書けない。
}

関数を async関数 として定義すると、返り値は Promise になります。
つまり、返り値で「処理が完了したかどうか」を判断できるようになります。

awaitのもうひとつの役割

さきほどから async関数 の返り値は Promise であると言っていますが、
じゃあ async関数 に設定していた返り値( async関数 を定義するときに return ooo;と書いて指定していた値)はどうやって取り出すのか?と気になるかと思います。

そこで、awaitのもうひとつの重要な役割がポイントになってきます。
await には「 async関数 が本来返り値にする予定だった値を取り出す」という役割があります。

(例)
async function async関数(){
  return '本来返り値にしたかったデータ';
}

const awaitをつけないパターン = async関数();
// ↑ 「awaitをつけないパターン」 には Promise が入っている。

const awaitをつけるパターン = await async関数();
// ↑ 「awaitをつけるパターン」 には '本来返り値にしたかったデータ' が入っている。

なので、awaitを利用することで、
処理の完了を待つこと&本来の返り値を取得することが、同時に実行できます。

ちなみに、awaitasync関数 の実行時に直接つけなければいけないわけではなく、
Promise に対してであれば使うことが可能です。

なので、一旦awaitをつけないパターンPromise を取得しておいて、
実際に返り値を使う必要があるときにawaitで返り値を取り出してあげると、
無駄のないコードが書けます。

(例)
const awaitをつけないパターン = 時間がかかる関数(); // 例: 大量のデータを取得してくるAPIなど。

// 時間がかかる関数() には await をつけていないため、処理がおわったかどうかに関わらず、次の処理が進んでいく。
// この間に実行しても問題ないような、他の処理を進めておく。

// 時間がかかる関数() の返り値を使う必要があるタイミングで await をつける。
const 時間がかかる関数の返り値 = await awaitをつけないパターン;

// こうすることで 「時間がかかる関数の処理が終わっていない場合だけ、処理が終わるのを待つ」 という流れにできる。

複数の非同期処理が完了するのをまとめて待ちたい場合

このような処理をしたい場合のやり方が、過去記事にまとめてありますので、参考にご覧ください。
3秒で理解する async/await関数 の並列実行

おわり

いかがでしょうか?
この記事をお読みいただいた方々が、
なんとなく思ったとおりにawaitを扱えるようになってくれたら幸いです。

記事を書いてる途中に期間をあけすぎてしまったため、内容が中途半端かもしれませんが一旦投稿します。
説明してなかったなっていう事を思い出したら、後日追記したいと思います。

ご拝読ありがとうございました。

31
22
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
31
22