###概要
サーバーからデータを受け取るアプリを作成した際にJavaScriptの順次実行に悪戦苦闘したので、その際に調べたことを本記事にまとめておきたいと思います。
###目次
###同期処理と非同期処理
JavaScriptでは、基本的に上から順に1つずつ実行されます。上から順に1つずつ実行することを同期処理と言います。次の sample1.js では、①を実行した後に②を実行しているので、以下のようになります。
sample1.js
console.log("start"); // ①
console.log("end"); // ②
>```console:sample1.jsの実行結果
start
end
次の sample2.js では、APIからデータを取得しています。データをまとめて送るなどといった処理はすべてサーバ側で行われ、データが送られてきます。このように他の場所で実行してくれる処理を非同期処理と言います。(※ sample2.js 以降ではjQueryも使用しています。)
sample2.js
$.ajax({
type: "GET",
url: "APIのURL",
data: "送信するデータ",
dataType: "送信するデータ型",
contentType: "受信するデータ型"
// 成功時の処理
}).done(function(response, textStatus, jqXHR){
var message = "success";
console.log(message);
});
>```console:sample2.jsの実行結果
success
それでは、同期処理と非同期処理を混ぜてみます。次の sample3.js はAPIからデータを取得し、その成否を表示しています。
sample3.js
main=()=>{
console.log("START"); // ①
var message = getData("info"); // ②
console.log(message); // ③
console.log("END"); // ④
}
// APIからデータ取得
getData=(sendData)=>{
console.log("getData begin");
$.ajax({
type: "GET",
url: "APIのURL",
data: sendData,
dataType: "受信するデータ型",
contentType: "送信するデータ型"
// 成功時の処理
}).done(function(response, textStatus, jqXHR){
var message = "success";
console.log("getData finish");
return message;
});
}
>```console:sample3.jsの予想結果
START
getData begin
getData finish
success
END
上から①、②、③、④の順に実行されると、上記の sample3.jsの予想結果 になります。しかし、非同期処理は実行を他の場所に任せている分、その間に次の処理を同時に行うことができ、並行処理のようになります。そのため、②でリクエストを送り、データを待つ間に、③や④を実行してしまいます。そのため、実際の実行結果は下記の sample3.jsの実行結果 になります。messageはgetDataが終わっていないため、何もないので、undefinedと表示されています。このように、非同期処理が終わるのを待たずに実行されてしまい、実行する順番が入れ替わってしまうことがあります。
sample3.jsの実行結果
START
getData begin
undefined
END
getData finish
上記の予想結果のようにするにはどうすればよいのでしょうか?
###<font color='#a0ea60'>順次実行</font>
非同期処理を上から順に実行するには、Promiseとasync/awaitを使用するのがおすすめです。<font color='#609040'> *sample3.js* </font>をPromiseとasync/awaitを用いて書き直したものが次の<font color='#609040'> *sample4.js* </font>になります。Promiseとasync/awaitを使用することで、awaitがついている関数の処理が終わるまで待ってくれます。
  Promiseとasync/awaitの使い方:
    1. 非同期処理をgetData(sendData)のように1つの関数にまとめておきます
    2. 順次実行したい非同期処理や同期処理をasyncで囲みます(①)
    3. 順次実行したい非同期処理の関数の前にawaitを書きます(②)
    4. 関数にまとめた非同期処理をPromiseで囲みます(③)
    5. 非同期処理が成功した時に返したい値などをresolveに入れます(④)
     ※入れられるのは1つだけなので、複数ある場合は配列にまとめてください
>```javascript:sample4.js
main=()=>{
(async()=>{ // ①
console.log("START");
var message = await getData("info"); // ②
console.log(message);
console.log("END");
})(); // ①
}
>// APIからデータ取得
getData=(sendData)=>{
return new Promise((resolve, reject)=>{ // ③
console.log("getData begin");
$.ajax({
type: "GET",
url: "APIのURL",
data: sendData,
dataType: "受信するデータ型",
contentType: "送信するデータ型"
// 成功時の処理
}).done(function(response, textStatus, jqXHR){
var message = "success";
console.log("getData finish");
resolve(message); // ④
});
});
}
sample4.jsの実行結果
START
getData begin
getData finish
success
END
###<font color='#a0ea60'>順次実行とエラー処理</font>
しかし、このままでは、エラー処理がされていません。<font color='#609040'> *sample4.js* </font>にエラー処理を追加してみたものが次の<font color='#609040'> *sample5.js* </font>です。
エラー処理の場合、asyncに囲まれている部分でエラーが発生した場合に行われる処理をcatchの中に書きます(①)。また、非同期処理の関数では、APIからエラーが返ってきた際の処理をAjaxの場合はfailに書きます(②)。そして、非同期処理が失敗した時に返したい値をrejectに入れます(③)。このrejectに入れた中身がasyncのcatchのerrの中身になります。
>```javascript:sample5.js
main=()=>{
(async()=>{
console.log("START");
var message = await getData("info");
console.log(message);
console.log("END");
})().catch((err)=>{ // ①
console.log(err);
console.log("ERROR END");
});
}
>// APIからデータ取得
getData=(sendData)=>{
return new Promise((resolve, reject)=>{
console.log("getData begin");
$.ajax({
type: "GET",
url: "APIのURL",
data: sendData,
dataType: "受信するデータ型",
contentType: "送信するデータ型"
// 成功時の処理
}).done((response,textStatus,jqXHR)=>{
var message = "success";
console.log("getData finish");
resolve(message);
// 失敗時の処理
}).fail((jqXHR, textStatus, errorThrown)=>{ // ②
var message = "failed";
console.log("getData finish");
reject(message); // ③
});
});
}
sample5.jsの実行結果(成功時)
START
getData begin
getData finish
success
END
>```console:sample5.jsの実行結果(失敗時)
START
getData begin
getData finish
failed
ERROR END
###複雑な順次実行
実はPromiseの中にasyncを書き、入れ子構造にすることができます。そのため、非同期処理の繰り返しも順次実行することができます。その一例を sample6.js に示します。
sample6.js
main=()=>{
(async()=>{
console.log("START");
var message = await repeat(["info", "name"]);
console.log(message);
console.log("END");
})().catch((err)=>{
console.log(err);
console.log("ERROR END");
});
}
// 繰り返し処理
repeat=(words)=>{
return new Promise((resolve,reject)=>{
(async()=>{
console.log("repeat begin");
var multiMessage = [];
for (var i=0; i<words.length; i++) {
var message = await getData(words[i]);
multiMessage.push(message);
}
console.log("repeat finish");
resolve(multiMessage);
})().catch((err)=>{
console.log("repeat error");
reject(err);
});
})
}
// APIからデータ取得
getData=(sendData)=>{
return new Promise((resolve, reject)=>{
console.log("getData begin");
$.ajax({
type: "GET",
url: "APIのURL",
data: sendData,
dataType: "受信するデータ型",
contentType: "送信するデータ型"
// 成功時の処理
}).done((response,textStatus,jqXHR)=>{
var message = "success";
console.log("getData finish");
resolve("success");
// 失敗時の処理
}).fail((jqXHR, textStatus, errorThrown)=>{
var message = "failed";
console.log("getData finish");
reject(message);
});
});
}
>```console:sample6.jsの実行結果(成功時)
START
repeat begin
getData begin // 1回目のgetData
getData finish
getData begin // 2回目のgetData
getData finish
repeat finish
["success", "success"] // 1回目のgetDataの結果と2回目のgetDataの結果
END
***
###<font color='#306010'>最後に</font>
今回、社内イベントで初めて記事を書かせていただきました。拙いところが多々あるかと思いますが、改善点等ございましたら、教えていただけると幸いです。
最後まで読んでくださり、ありがとうございました。<font color='#fff0e0'>ヾ(。>﹏<。)ノ゙</font>
***
###<font color='#306010'>参考文献</font>
本記事を書くにあたり、参考にさせていただきました
- [Qiita マークダウン記法 一覧表・チートシート](https://qiita.com/kamorits/items/6f342da395ad57468ae3)
- [Markdown記法 チートシート](https://qiita.com/Qiita/items/c686397e4a0f4f11683d)
- [JavaScriptの同期、非同期、コールバック、プロミス辺りを整理してみる](https://qiita.com/YoshikiNakamura/items/732ded26c85a7f771a27)