#はじめに
今回はJavaScriptの非同期処理について解説していきます。
最初に非同期処理とはという解説をした後に、Promiseを使って制御する方法とasync/awaitを使って制御する方法を解説します。
頑張っていきましょう。
#非同期処理とは
そもそも非同期処理とは一体なんでしょうか。
以下のサイトに分かりやすい図があったので拝借しました。
非同期処理を端的に言うなら、あるタスクが実行しているときに、他のタスクが別の処理を実行できる方式
ということができます。
プログラムを例にして、詳しく解説していきます。
普通、プログラムを実行すると、コードを上から順に一行ずつ実行していきます。
これは普段から当たり前のように使っている同期処理
と呼ばれるものです。
しかし、サーバーと通信を行った時などは、この同期処理
とは違った挙動をします。
具体的には、サーバーと通信を行う(fetchなど)行を実行したあと、その終了を待たずに次の行が実行されることになります。
例えば、fetchを使ってサーバーからデータを取ってきた後に、そのデータを出力するというプログラムを書いたとします。
その時、非同期処理によりfetchが完了する前に次のコードが実行されるため、データが出力できない(undefainedになる)です。
以下のコードで、非同期処理を制御せずにfetchメソッドを用いてgithubからユーザーIDを取得して、出力してみましょう。ちなみに、外部からメソッドをimportするためESModuleであることをpackage.json内に示しておきましょう。
{"type": "module"}
import fetch from 'node-fetch';
const getGitUsername = () => {
const url = 'https://api.github.com/users/hangi4343'
fetch(url).then(res => res.json())
.then(json => {
console.log('非同期処理成功')
return json.login
}).catch(error => {
console.error('非同期処理失敗', error)
return null
})
}
const message = 'GitのユーザIDは'
const username = getGitUsername()
console.log(message + username)
GitのユーザIDはundefined
非同期処理成功
const username = getGitUsername()
の行でgetGitUsername関数の完了を待たずに、その下のconsole.log(message + username)
が実行されるためusernameがundefainedになってしまいます。
fetchの使い方についてはこちらの記事を参考にして下さい。
簡単に解説すると、fetchが返すResponse
オブジェクトを最初のthenで受け取り、その中でres.json()
をreturnしています。その後、それをまたthen受け取り、メッセージを出力した後にjson.login
がreturnされます。また、この処理内でエラーが起きると、そのエラーをcatchで受け取り、その後の処理を実行します。
このコードが上手く実行されるために、const username = getGitUsername()
の非同期処理を行う処理を、完了まで待ってから次のコードを実行することが考えられます。
この非同期処理の完了を待つために、Promiseやasync/awaitが用いられます。
#Promiseで非同期処理を制御
それでは、非同期処理を制御していきましょう。
以下の記事を参考にしました。
Promiseはresolveとrejectの2つの関数を引数に取ります。
- resolve : 処理が成功したときのメッセージを表示する関数
- reject : 処理が失敗したときのメッセージを表示する関数
import fetch from 'node-fetch';
const getGitUsername = () => {
return new Promise((resolve, reject) => {
const url = 'https://api.github.com/users/hangi4343'
fetch(url).then(res => res.json())
.then(json => {
console.log('非同期処理成功')
return resolve(json.login)
}).catch(error => {
console.error('非同期処理失敗', error)
return reject("reject")
})
})
}
const message = 'GitのユーザIDは'
getGitUsername().then(username => {
console.log(message + username)
})
非同期処理成功
GitのユーザIDはhangi4343
非同期処理を制御することができました。
コードについて解説します。
Promiseは、resolveとrejectの2つの引数を持ちます。
getGitUsernameを実行すると、Promiseオブジェクトがreturnされます。
このPromiseオブジェクトに対してthen
を用いると、そのthenの引数にPromiseオブジェクトのresolve
でreturnされた値が代入されます。
もう少し詳しく解説します。Promiseのコンストラクタの中でresolveが呼ばれると、Promiseはresolveの状態になります。thenはPromiseの状態がresolveになったときに呼ばれるので、結果的にthenの引数にresolveでreturnされた値が代入されることになります。
この場合では、getGitUsername().then(username)=>
の部分で、returnで帰ってきたPromiseオブジェクトに対してthenを実行し、resolve(json.login)
のjson.login
がthenの引数であるusernameに代入されます。
今回のコードでは示していませんが、getGitUsername.then.catch(reject => console.log(reject))
のようにすると、Promiseのコンストラクタの中でrejectが呼ばれたときに、Promiseがrejectの状態になり、catch内の処理が実行されます(catchはPromiseがrejectの状態のときに呼ばれるため)。
Promiseはresoveとrejectとpendingの三つの状態を持ちます。returnしたPromiseオブジェクトに対してthenを実行すると、Promiseオブジェクトがresolveの状態になるまで待ってから
then以降の処理が実行されます。
また、catchを実行するとPromiseオブジェクトがrejectの状態になるまで待ってから
catch以降の処理が実行されます。
また、このthenやcatchの処理は非同期処理になるため、resolveやrejectが返される前にその次のコードが実行され、resolveやrejectが返ってきて初めて実行されます。
#async/awaitを使って非同期処理を制御
今度は、async/awaitを使って非同期処理を制御してみましょう。
こちらの記事を参考にしました。
以下のコードです。
import fetch from 'node-fetch';
const getGitUsername = async () => {
const message = 'GitのユーザーIDは';
const url = 'https://api.github.com/users/hangi4343'
const json = await fetch(url)
.then(res => {
console.log('非同期処理成功')
return res.json()
}).catch(error => {
console.error('非同期処理失敗', error)
return null
})
console.log(message + json.login)
}
getGitUsername()
console.log('end')
end
非同期処理成功
GitのユーザーIDはhangi4343
それではコードの解説です。
asyncは、関数の前に書きます。このasyncにより、その関数の中でawaitが使えるようになります。
awaitは端的にいうと、非同期処理を同期処理のように書けるようにしてくれるもの
という風に説明されます。
今回はconst json = await fetch(url)
の部分でawaitを使っています。awaitにより、fetch(url)
の部分で値がreturnするまで待つことができます。
そのため、値が返ってきたあとにconsole.log(message + json.login)
の部分のコードが実行されます。
しかし、awaitはあくまでPromiseを簡単に扱えるようにしたものであり、内部的にはawaitの右側の式を同期的に実行した後、resolveしたpromiseを生成し、awaitの左側および下のコード全てをthenの引数の関数として実行しています。
つまり、この場合においてはconsole.log(message + json.login)
の部分がPromiseにおけるthenの後に実行されるものであり、console.log('end')
の部分はthenの後の処理ではありません。
つまり、getGitUsername()
の実行の完了を待たずに、console.log('end')
は実行されます。
#終わりに
今回の記事はここまでになります。
お疲れさまでした。