全ての初心者エンジニアに知って欲しい!非同期処理とは?
概要
メンターのヒグです!前回に引き続き、今日はメンティーのRockyさんから、「非同期処理」について教えて欲しいというご要望がありましたので、そのやり取りの模様を記事にして、ご紹介したいと思います。
本題
Rocky:ヒグさん、この間はOpenID Connectのお話有難うございました! で、その案件で、フロント側の処理として、恐らく非同期処理を実装しないといけないような気がしてきているのですが、ちょっとよく分からなくて、悩み中なんですよね。。
具体的に言うと、ログインしているのかどうかの判定をしてから、ある処理を走らせたいのですが、そのログインしているのかどうかの判定の前に、ある処理が走ってしまうんですよね。。処理したい順に上から順番に書いてはいるんですが。。。で、これはもしや非同期処理ってやつをかまさないといけないのかな!?と思った次第なんです(笑) Promiseとかってやつ。。。
ということで、今日は非同期処理について、ご教示頂くことは可能でしょうか??
Higu:非同期処理ですね! これはアルアルな躓きポイントですよ! 非同期処理を制することができれば、JavaScriptで実装できる幅も広がると思うので、一緒に見て行きましょう!
Rocky:はい、是非よろしくお願いします!
Higu:まずそもそもなのですが、JavaScriptはシングルスレッド処理をする言語だっていうところから見て行きましょう!
Rocky:はい!
Higu:JavaScriptは下の図のようなイメージで処理が進んでいくんですよね!
Higu:並列に処理を進めていくのではなく、一つの処理が終わったら次の処理、その処理が終わったら次の処理という感じで進めていく感じですね。
なので、マルチスレッドという処理の形式もあって、それは下の画像のような感じで、並列して処理が進んでいく感じになります。
Rocky:なるほど。
Higu:で、JavaScriptはシングルスレッドで実行されるにも関わらず、Rockyさんは上から書いた順番に実行されなかったんですよね?
Rocky:そうなんですよ(泣)
Higu:何でだと思いますか?
Rocky:そうですね〜、JavaScriptはブラウザで動く言語なので、例えば画像の読み込みとかは時間かかっちゃうんで、時間がかかるものについては後回しにする、と同じような雰囲気でしょうか?
Higu:あぁ〜、近いニュアンスなんですが、少し違いますね。
例えば、シングルスレッドで上からこういう感じできましたと。で、下図のようにAPIを叩く処理を入れるとします。で、それに10秒かかるとしますと。そうすると、その間10秒間待ってくれずに、処理は進んでしまうんですよ!
Rocky:マジっすか!? そこ待ってくれないんすか(泣)
Higu:そうなんです。待ってくれないんです(笑) 勝手に進んでしまって、えっ、ここかよ!?ってなってしまうんです。この同期的(逐次的)ではなく処理されてしまうものが非同期処理であって、ここで待ってもらうためにはどうしたらいいのかっていうと、Promiseっていうのを使うってことになります。Promiseっていうのは約束っていう意味ですよね?「私、あなたのこと待ってる!」っていう約束をするってことなんですね。
Rocky:なるほど〜! これが噂のPromiseの使い処なんですね!
Higu:ですです! ではPromiseを使ってどんな感じで非同期処理ができるのか見てみましょう!
Rocky:はいお願いします! あっ、あと僕が担当しているサイト、IEにも対応しなければならなくてですね、、、
Higu:なるほど(苦笑)、ではその辺も考慮しつつ、サンプルを作ってみましょう!
Rocky:あざーっす!!
Higu:では、APIへのリクエストは「XMLHttpRequest」を使ってやってみましょっか。
Rocky:ですね!
Higu:ではまず、サンプルを実装する前に、Promise構文に付いて簡単に説明しておきたいと思います。
new Promise(
).then(
).catch(
).finally(
);
上記のような感じで、new PromiseでPromiseをインスタンス化してあげて、そしてそのPromiseのthenメソッド、catchメソッド、finallyメソッドというのを使って、非同期処理に対して制御を加えていくという感じになります。
Rocky:ふむふむ。
Higu:で、次に、Promiseの引数として、callback関数を設定してあげます。
new Promise(function(resolve, reject) {
}).then(
).catch(
).finally(
);
このcallback関数には、resolveとrejectの2つの引数をとることになります。
resolveが取られた時は、
new Promise(function(resolve, reject) {
resolve('hello');
}).then(function(data) {
}).catch(
).finally(
);
thenメソッドの中のcallback関数が実行されることとなり、こちらのcallback関数の引数には、resolveで渡した実引数、この場合ですとhelloが渡ってくることになります。
なので、↓ こういうことになります。
new Promise(function(resolve, reject) {
resolve('hello');
}).then(function(data) {
console.log(data) // -> "hello"
}).catch(
).finally(
);
そして、thenメソッドが完了すると、次にあるcatchメソッドというのは、この場合スキップされて、finallyメソッドに処理が移ります。
このメソッドでは終了処理を記述していきます。まぁ、↓こんな感じですね。
new Promise(function(resolve, reject) {
resolve("hello");
}).then(function(data) {
console.log(data) // -> "hello"
}).catch(
).finally(function() {
console.log("終了処理")
});
Rocky:なるほど〜
Higu:それでは次に、Promiseのcallback関数で、rejectが実行された時はどうなるのか?
rejectはPromiseのcallback関数でなんらかのエラーが発生した時に、それをPromiseに通知してあげるために、使用するメソッドとなります。
new Promise(function(resolve, reject) {
reject("bye")
}).then(
).catch(function(data) {
console.log(data) // -> "bye"
}).finally(function() {
console.log("終了処理")
});
rejectが呼ばれた場合には、catchメソッドの中のcallback関数が実行されて、rejectで渡した引数が渡ってくることになります。
そしてcatchメソッドの中のcallback関数が実行された後に、先程と同じようにfinallyメソッドに渡したcallback関数が実行されます。
Rocky:ふむふむ
Higu:で、簡単にまとめてみますと、Promise構文っていうのは、↓こんな感じの造りになっていて、
new Promise(
同期処理
).then(
非同期処理(resolveを待つ)
).catch(
非同期処理(rejectを待つ)
).finally(
非同期処理(then, またはcatchを待つ)
);
new Promiseに与えた引数の中のcallback関数というのは、同期的に処理されるんですが、thenメソッドやcatchメソッド、finallyメソッドのcallback関数は、全て非同期で処理されることになるんです!
Rocky:おぉ〜
Higu:ポイントなのは、resolveやrejectが呼ばれるまでは、後続のthenやcatchは実行されないので、この特徴を利用することによって、Promiseのcallbackに非同期処理を組み込むことができるってことになります!
Rocky:完全に理解できました!
Higu:では、以上を踏まえて、サンプルのAPIを叩いて、データを受け取ったあとに、何か処理を走らせるということをやってみましょうか!
Rockyさんの案件の場合の、「ログイン判定の処理」っていうのを、ここでは「サンプルAPIを叩いて、データの取得が完了するまで」と、置き換えて考えてみて下さい。
Rocky:なるほど!
Higu:こんな感じになりますかね。
console.log('処理開始')
function testFunc() {
return new Promise(function(resolve) {
var request = new XMLHttpRequest();
request.open('GET', 'https://jsonplaceholder.typicode.com/users/1', true);
request.responseType = 'json';
request.onload = function () {
var data = this.response;
console.log(data);
resolve(data);
};
request.send();
})
}
testFunc().then(function(data) {
console.log('API通信成功後の処理');
console.log(data);
}).catch(function(data) {
console.log('エラーです')
}).finally(function() {
console.log('処理終了')
});
上の基礎編でやったことと少し形は違っていますが、Promiseインスタンスを作成してreturnで返すという部分を関数化してあります。
で、その関数の呼び出し元のところで、thenメソッドで処理を繋げっているって感じになります。なので、やっていることは同じです。
Rocky:あ〜、そう、そうなんです、このreturnで何を返しているのか、とかを理解するのが、僕ちょっと苦手で、最近は結構意識して見るようにしてます!
Higu;いい心掛けだと思います!上のコードについて少し補足すると、request.openのところで、第三引数をtrueにしていますが、trueにすることで、リクエストを非同期に処理することを指定しています。また、request.send(); で、実際にリクエストを開始しています。
で、これを実行すると、下のような結果になります。
APIからのデータ(data)の取得が完了するのを待って、それが完了した後に、thenメソッドのcallbackを走らせると。
で、その取得したデータ(data)も、thenメソッドでの処理に渡すことができているというわけになるんですね!
なので、Rockyさんの案件で言うと、Promiseのcallbackにログインがどうかを判定させる処理を書いて、thenメソッドのcallbackにログイン判定後に走らせたい処理を書くって感じになり、その判定結果をthenメソッドに渡してやることができるっていう感じになるかと思います!
Rocky:なるほどです!これを会社でやってる案件に活かすかたちに改造してみますね!今日はお忙しいところどうも有難うございました!
Higu:こちらこそどうも有難うございました!
最後に
今回は多くのJS初心者が悩みやすい非同期処理について紹介してみました。自分もそうだったんですが本当にみんなつまずきます。今回ここで非同期処理について理解が深まれば、中級者への道も開けると思います。また、ブログをみるだけではなく実際に手を動かしてみてくださいね!