目的
いつまで経っても非同期処理がよくわからなかったので一念発起して学び直し。
まだまだ理解不足なので間違いあればご教示お願いします。
結論
非同期処理とは、コールスタックが空になってから実行される処理のこと。
どういうことか順を追って記載していく。
同期処理との違い
メインスレッド(JavaScriptが実行される場所)でコードが上から順番に実行されることを同期処理と呼ぶ。
上から順番に実行されるので、長い処理があってもそれが完了してからでないとその次の処理には進まない。
対して非同期処理は、
処理の完了を待たずして次の処理を実行を移させる処理のことを指す。
非同期処理の仕組み
非同期処理は、その処理の完了を待たずして次の処理へ実行を移すため、
並列で処理が複数進行できるようにしたもの、と思われがちだが、(私もそうだった)
そうではない。
JavaScriptは基本的にメインスレッドのみで実行されるからである。(=シングルスレッドで動いている)
(厳密に言うと並列で処理できる方法もある、気になる方はService Workerで検索)
なぜそんなことができるかというと、非同期処理は
一時的にメインスレッドから処理が移動されるから、である。
JavaScriptの仕組みをおさらい
JavaScriptはどのようにして実行されるのか。
その前に以下の用語を押さえます。
スレッド(コールスタック)
JavaScriptの処理の実行場所。
Last in Last Out(後から入ってきた処理が先に出る)
本が積まれているみたいな状況を思い浮かべると良い。(上に積んだものをどかさないと下の本が取れない)
イメージはこんな箱↓
| |
| |
| |
------
タスクキュー
実行待ちの処理が並んでいる場所。
コールスタックが空になってから、タスクキューの処理がコールスタックに移動される。
First IN First OUT(先に入ってきた処理が先に出る)
これらの仕組みをイメージすると非同期処理はかなりわかりやすいです。
以下のコードがあったとする。
funtion test() {
setTimeout(function(){console.log("setTimeoutの中の人です")}, 3000);
}
console.log('hello');
test();
console.log('yes');
- console.log('hello');の処理がコールスタックに格納される
- helloをログに出力
- console.log('hello');は処理が完了したのでコールスタックから消える
- test()がコールスタックに格納される
- setTimeoutはWebAPIに投げられる、3秒経ったら、タスクキューに追加される
- test()は処理がなくなったのでコールスタックから消える
- console.log('yes');の処理がコールスタックに格納される
- yesをログに出力
- console.log('yes');は処理が完了したのでコールスタックから消える
- コールスタックが空になったため、タスクキューからfunction(){console.log("setTimeoutの中の人です")}が追加されて処理...
文章にしてみるとかなりわかりにくいため以下の記事を読むとわかりやすいです。
素晴らしい記事をありがとうございます。
Promise
非同期処理が完了した際に結果を返すオブジェクト。
本来ならば、完了を待たずして次へ処理を移してしまう非同期処理の完了を待ってから
実行したい処理がある場合に使う。
引数にresloveとrejectというコールバック関数を持ち、
resloveが実行されると、その後then内の処理が実行される。
rejectが実行されると、その後catch内の処理が実行される。
すなわち、thenやcatchの中に非同期処理の完了後に実行したい処理を書ける。
new Promise(function(resolve, reject){
console.log('Promise');
resolve(1);
}).then(function() {
console.log('非同期処理の後に行いたい処理')
}
console.log('end');
Promise
end
非同期処理の後に行いたい処理
一つ、ややこしいのはPromise内のコールバック関数は同期的に実行されるため、
最後のendよりも先に実行される。
then以降は、上から順番に実行されるのではなく、resolveされてから実行される。
thenで繋げたい場合は、
必ずPromiseを返すようにする。
Promiseがreturnされないと非同期処理の完了を待たずに次の処理が実行されてしまう。
... ここで有る疑問が浮かぶ。
thenはresolveされてから実行されるのであれば、console.log('end');よりもthenの中の方が先に実行されるのでは説
thenは非同期関数なので、resolveされたらタスクキューに積まれる。
つまりコールスタックが空になってから出ないと実行できない。
そのためコールスタックにconsole.log('end');があるかぎりは実行できず、空っぽになってから実行される。
async await
Promiseのさらに便利にした機能、くらいで思って良い気がする。
非同期処理の完了の後に実行したい処理が有るときに使える便利機能。
function test(val) {
return new Promise(function (resolve, reject) {
console.log("Promise");
val++;
resolve(val);
});
}
async function init(val) {
console.log("init");
let a = await test(val);
console.log("init " + a);
return a;
}
init(1).then(function (val) {
console.log("then " + val);
});
console.log('global');
init
Promise
global
init 2
then 2
test()が終わった後にconsole.log("init " + a)をしたい場合
- Promiseを返却させるtestを宣言する。
- test()の呼び出し位置にawaitをつける。こうすることでPromiseが返ってくるまで次の処理へは行かせない。
- awaitはasyncをつけた関数内でしか使用できないため、initにasyncを付ける。
- asyncも返り値をPromiseでラップして返すので、initの後にthenで繋げることも可能。
まとめ
いやー非同期処理、むずすぎる。
調べれば調べるほど非同期処理って何?となる。
一旦非同期処理の順番だけわかったからよしとする。
コールスタックや、タスクキューを勉強すると
非同期処理の処理順に関しては理解しやすいかも。