https://www.youtube.com/watch?v=kbKIENQKuxQ
「非同期処理?何それ美味しいの?」状態なので、
上記の動画で学習しつつ、いい感じに記事にしてみたいと思います。
非同期処理発明までの流れを簡単に説明。
プログラムが上から順に実行される。
getData('url'); //データをとってきて
show(); //それを表示する
データが取り終わるまで、画面が真っ白なまま表示されない!!
普通のプログラムは上から順に実行されるから。
一行づつ処理が完了してから次の行を実行するという流れがある。
データを取り終えてから
getData('url');
ページを表示する
show();
なので、普通のプログラムだと、データが取り終わるまで、画面が真っ白なまま表示されない。
バックグラウンドで処理を行おう!!
バックグラウンド処理:コンピュータを使っている人からは見えない所でコッソリやっている処理のこと。
データをとってくる処理は一旦裏に回して
getData('url');
先に画面を表示しておいて、
show();
データがとって来れたらそのデータを画面に反映しよう。みたいな感じ
getData('url');
こうすることで、画面が真っ白な状態のまま、ユーザーを待たせるということを防ぐことができる。
このように、ある処理を一度裏に回して、別の処理を実行しておいて、処理が完了したらその内容を反映させるというやり方を
『非同期処理』と呼ぶ。
結論:コールバック関数を使う!
コールバック関数:「この関数、おまえの方で呼び出してや~」と関数に引数として渡される関数のこと
こんな感じで書くと
setTimeout(() => console.log(1), 1000); //1秒後にコンソールに「1」と表示する関数(コールバック関数)を実行するよ
console.log(2); //コンソールに「2」と表示するよ
コンソール画面では
2 //の後に
1 //が表示される
コールバック関数を使用すると、非同期にして処理のタイミングをずらすことが可能になる。
人類:コールバック関数によって非同期を手に入れたったで!
めでたしめでたし。と、なるはずだったがそうもいかず...
→非同期処理を順々に実行するのが複雑になる。
例えば、バックグラウンドで行われている複数の処理を順に実行したい時がある。
具体例:カウントダウンタイマー
setTimeout(() => console.log(3), 1000);
setTimeout(() => console.log(2), 1000);
setTimeout(() => console.log(1), 1000);
「一秒ずつ3、2、1とログを出力していきたい」みたいな意図がコードから取れるけど、
実際のコンソール画面では
3
2
1
一秒後に全てが一斉に表示される。
結論:こんな感じ↓
setTimeout(() => {
console.log(3);
setTimeout(() => {
console.log(2);
setTimeout(() => {
console.log(1);
}, 1000);
}, 1000);
}, 1000);
人類:あれ?読むのめんどくね?
そう、this is コールバック地獄
処理が増えれば増えるほどこの入れ子(処理の中に処理がある)構造が深くなって読みにくい!
人類:なんかこれをいい感じにできるやつおらんの?
...?:ハッハッハ!、我に任せい!
人類:だ、誰やお前!
Promise:Promiseじゃ〜!連続した非同期処理をフラットに書けるで〜!
「後で値を返すから待っててね」という約束。
段階によって、「待っててね」「完了したよ」「失敗したよ」みたいに状態を持つ。
Promiseの基本的な書き方
//成功時
new Promise((resolve, reject) => {
resolve('成功');
});
//失敗時
new Promise((resolve, reject) => {
reject('失敗');
});
Promiseはresolveとreject、ふたつの関数を引数に取る。
resolve:処理が成功したときのメッセージを表示する関数
reject:処理が失敗したときのメッセージを表示する関数
返すのはresolveだけでいい時はこんな感じに省略して書くこともできる。
new Promise(resolve => resolve('成功'));
完了時の値を.thenで繋げて次の処理に渡すこともできる。
new Promise(resolve => resolve('こんにちは'))
.then(res => console.log(res)); //「こんにちは」と出力される
カウントダウンタイマーの例をPromiseで表すと↓みたいな感じになる。
new Promise(resolve => {
setTimeout(() => {
console.log(3);
resolve();
}, 1000)
}).then(() => {
return new Promise(resolve => {
setTimeout(() => {
console.log(2);
resolve();
}, 1000);
});
}).then(() => {
return new Promise(resolve => {
setTimeout(() => {
console.log(1);
resolve();
}, 1000)
});
});
人類:あれ?あんま見やすくなくね?thenで繋げてもコードが長くなるだけで正直ビミョい。
Promiseのthen君:な、ナニィ?
人類:誰かもっといい感じにしてや〜
...?:どけどけどけィ!
人類&Promiseのthen君:誰やお前!
async/await:デュボボボボボ!async/awaitじゃ〜!連続した非同期処理を簡潔にかけるで〜!
人類&Promiseのthen君:どんな感じでかけるんや!見せてみぃ!
async/await:よかろう!でデーン!!
func = async () => {
await log(3);
await log(2);
await log(1);
}
log = (num) => {
return new Promise(resolve => {
setTimeout(() => {
console.log(num);
resolve();
}, 1000);
});
}
func();
結論:関数に対してasyncを指定することで、その関数が非同期関数であることを表している。
func = async () => {
}
結論:関数の前にawaitを指定することで、Promise(resolve or reject)が返ってくるまで次の処理を実行させないようにしている。
func = async () => {
await log(3)
}
結論:コールバック地獄、Promiseをthenで繋げるめんどくささから抜け出して、めちゃシンプルに非同期処理を記述できる。
func = async () => { //非同期処理を行う関数ですよ
await log(3); //Promiseが返ってくるまで待ちますよ
await log(2);
await log(1);
}
log = (num) => {
return new Promise(resolve => { //関数logはPromiseを返しますよ
setTimeout(() => {
console.log(num); //引数に受け取ったnumをコンソールに出力しますよ
resolve(); //resolve(成功)のPromiseを返しますよ
}, 1000); //出力は1秒後ですよ
});
}
func(); //関数実行
Promiseのthen君:(°_°)
人類:async/awaitしか勝たん
通常のプログラムで大きなデータをとってくる時に、一旦処理が止まってユーザーの見ている画面が真っ白になってしまったりするのを防ぐために非同期処理が生まれた。非同期処理はコールバック関数によって実現することができた。
コールバック関数で非同期処理を実行する際、処理が多くなればなるほど入れ子構造が深くなっていき複雑になるという問題があった。(コールバック地獄)
コールバック地獄を解決するための処理としてPromiseが生まれた。非同期処理を.thenで繋げて、入れ子構造ではなく並立に記述することができるようになった。でもこの記述がいまいち見にくかった。
asyncで非同期関数であることを宣言。awaitでpromiseを待つことで、ほぼ普通の関数のような記述で非同期処理を行うことができるようになった。
今日の朝は「非同期処理?何それ美味しいの?」状態だったのですが非同期処理についていい感じに理解できた感じがしております。
最後までご覧いただきありがとうございました〜!
@ 酒井悠宇