非同期処理ってなに?なんで必要?
難しい単語ですよね。よくわかる解説をどうぞ
// 1. 重い処理(3秒かかっちゃう)
console.log('重い処理');
// 2. かるい処理(すぐおわる)
console.log('かるい処理');
こういう並びでコードを実行したかったり
// 1. 重い処理
console.log('重いよぅ');
// 2. その次じゃないとやっちゃいけない処理
console.log('重い処理おわらないとやっちゃだめー!');
コードの並び順通りに実行しないとダメ!って場合、ありません?
例えば
// 1. サーバとかDBから値をもらう
// 2. もらった値をコネコネする
みたいな処理をしたい場合、サーバから値もらってないのにコネコネできないよね。
ほかにも
// Aさんが操作する
// Aさんのターンが終わってからBさんのばん。Aさん終わるまでBさん触っちゃダメ。
ってとき、行の順番通りにコードを動かしたいし、上の行が終わるの待ちたいと。
え、そもそも待たないんだ!?って人もいるかもですね。その通り、待てば問題なし。
Ajaxや非同期処理を使いたい
待てばいいんだけどさ、待ったら以降すべての処理がとまっちゃいますよ、と。
とても遅いコードになってしまうし、重い画面になる。
なので、サーバ接続したりする処理だけ別枠にしたい。そうです。待たないんです。
画面を描画する
、サーバから値をもらう
。「同時に」やってしまうっていうのが、「非同期処理」の便利なところだな。
つまり、「サーバから値もらう」というのと、「値が帰ってきたら = 処理が終わったら 画面に表示する」を
順番通りに、直列にやりたいよね。
そう。
非同期処理が世の中で流行ってるから、非同期処理の中で、直列に処理したい
ということなのだ。
「重い処理」を実際に試してみましょう。
chromeでF12押して、devツールのコンソールのとこでjsが簡単に実行できますよね。試してみてね。
// 1. 重い処理(3秒後にconsole.logが実行されるよ)
setTimeout(() => console.log('重い処理おわる'), 3000);
// 2. その次じゃないとやっちゃいけない処理
console.log('重い処理おわらないとやっちゃだめー!');
結果は
重い処理おわらないとやっちゃだめー!
重い処理おわる
となっちゃう。試した?ほんと?
試してないじゃん。しょうがないなぁ。ほれこれ見て。
See the Pen Untitled by serna37 (@serna37) on CodePen.
重い処理, その後の処理
となってほしいのに、「, その後の処理」となったあとで「重い処理」となってしまっているね。
実はこれは、setTimeouが非同期処理なんですね。
3秒まつ、という処理が
「上の行から順番に実行し始めるけど、その行が終わってなくてもドンドン次にいってしまう」のです。
ここでいえば
1. 「3秒待ってからconsole.log(重い処理)」を実行開始
2. console.log(その後の処理)が実行開始
3. console.log(その後の処理)がクッソはやく処理完了する
4. ようやく3秒たって、console.log(重い処理)がやっと処理完了する
といった流れ。
わざわざsetTimeoutで遅れさせているけれど、サーバから値をとる、などの重い処理の代わりになっているはず。
前置きが長くなりましたが、これを解決するために「非同期処理」というものがあります。
では非同期処理ってなに?
やりたいことはこう
// 1-a. 重い処理をやる
// 1-b. 重い処理が終わるまで待つ
// 2. 次の処理にいく
ここで登場するのがPromiseです。
jsにはPromiseってものがあるのです。偉い人が作ってくれました。ありがとう偉い人。
Promiseの正確な説明はMDNのサイトを見てね。
new Promise(resolve => {
// 重い処理
setTimeout(() => {
console.log('重い処理おわる')
// resolveを実行したら次にすすむよ
resolve();
}, 3000);
})
.then(() => console.log('その後の処理'));
See the Pen Untitled by serna37 (@serna37) on CodePen.
解決しましたね。
無事、重い処理が終わるまで待ってから、次の処理を開始できました。
Promiseの使い方
公式がすべてですが一応解説。
覚えておくことは2つで、
・new Promise()
は引数に関数をとる
・resolve()
を実行すると、そこで終わる。
くらいですね。(エラー時の挙動とかは今回は省きます。公式見てね)
Promiseオブジェクトは、中で「状態」を持っています。new Promiseしたオブジェクト自体をconsole.logしてみるとよくわかる。
resolveしないままだと、PromiseStateとやらが"pending"になっている。保留してくれているのだ。
じゃあresolve()すると
PromiseStateが"fulfilled"になっているね。完了したで!って意味。次の処理行っていいよ!ってことだね。
だから、つなぎまくってこんなこともできる
new Promise(resolve => {
setTimeout(() => {
console.log('1番目');
resolve();
}, 3000);
})
.then(() => new Promise(resolve => {
setTimeout(() => {
console.log('2番目');
resolve();
}, 3000);
}))
.then(() => new Promise(resolve => {
setTimeout(() => {
console.log('3番目');
resolve();
}, 3000);
}))
.then(() => new Promise(resolve => {
setTimeout(() => {
console.log('4番目');
resolve();
}, 3000);
}))
;
なげぇ...
もっときれいに書きたいね。
直列実行
ようやくタイトル回収。きれいに書きましょう。
/**
* 関数を直列に同期実行. 以下の形式の関数の配列を引数とする.
* (v, r) => {任意の実装, r(v)を実行したタイミングで完了}
* 例)
* const sampleFunction = (v, r) => {
* // 任意の実装
* setTimeout(() => {
* // 遅延して完了し, 次の関数へ
r(v);
* }, 1000);
* }
* [sampleFunction, sampleFunction, ...]が引数となる.
*
* @param arr 直列実行する関数の配列 (index順)
* @return arrを全て実行したあとのPromiseを返却
*/
const seriesExe = arr => {
return arr
.reduce((x, y) => x.then(v => new Promise(r => y(v, r))),
Promise.resolve());
};
こいつらを直列に実行してみよう
let test1 = (v, r) => {
setTimeout(() => {
console.log('test1');
r('test2');
}, 3000);
};
let test2 = (v, r) => {
setTimeout(() => {
console.log(v);
r('test3');
}, 3000);
};
let test3 = (v, r) => {
setTimeout(() => {
console.log(v);
r('end');
}, 3000);
};
let ending = (v, r) => {
setTimeout(() => {
console.log(v);
r();
}, 3000);
};
seriesExe([test1, test2, test3, ending]);
// このあとにさらに続けることもできる PromiseAllとかしてもいいね
seriesExe([test1, test2, test3, ending]).then(() => ..(略).);
サンプルデモ
See the Pen Untitled by serna37 (@serna37) on CodePen.
ということでした。ちょいちょい大事なとこ飛ばしてるけど、詳しいところは公式読みつつ手を動かしつつ理解しましょう!
いじょうです