きっかけ
仕事でvue.js
を使うことになって非同期処理について勉強していたところ、Promise
とかasync
とかawait
とかで?????となったのでふんわりな理解だったコールバック関数について学び直そうと決意
現状の私の理解
-
コールバック関数
は関数に入れる関数。それ以外は...
非同期処理でやれることの例
- ボタンがクリックされた後に何かを表示する
- ファイルの読み込みが完了した後に何かをする
非同期処理のイメージ
-
処理を関数単位で考えた時
- 実行中の関数の中で非同期処理が発火する
- 実行中の関数の終了を持ってコールバック関数を実行する
※こちらのイメージは拾い画なのですが拾い元が特定できなくてリンクを載せることができません。。。知ってる方いましたら教えて...
関数を実行するときと値として扱う際の書き方の違い
- カッコ【()】をつけるかつけないか
hoge.js
const max1 = Math.max(1, 2); // これはカッコがついてるので関数の計算結果が入る
const max2 = Math.max; // これはカッコがないので関数自体が入る
- カッコなしを実行しようとすると
not a function
エラーが発生する
相手に実行してもらうのがコールバック関数
- 以下で考えるとconsole.logがコールバック関数となる。
huga.js
setTimeout(function() {
console.log('Hello!');
}, 2000);
関数を受け取る関数を高階関数と呼ぶ
- さっきのコードで言うと
setTimeout
が高階関数。
非同期処理の書き方
- 1.非同期処理関数はコールバック関数を受け取る高階関数にする
- 2.利用者は「終わったら実行したい処理」をコールバック関数として渡す
- 3.非同期処理関数は処理が終わったらコールバック関数を呼び出す
DOMのイベントで非同期処理
- クリックイベントにコールバック関数(console.log)を紐付け
piyo.js
document.querySelector('.my-button').addEventListener('click', function(event) {
console.log('clicked!');
});
- 関数名でも紐付け可能
puyo.js
function callback(event) {
console.log('Hello'!);
}
document.querySelector('.my-button').addEventListener('click', callback);
非同期処理を記述するPromiseの基本
- 今までの内容で非同期処理には高階関数とコールバック関数が必要なことがわかったと思うが、処理が多くなるとネストが多段になって読みにくくなってしまう。
- そこで
Promise
と言う仕組みを使うことで簡潔に記述できる - 使い方とサンプルは以下
- 1.まずPromiseをnewすることによりPromiseオブジェクトを作成。
- 2.Promiseのコンストラクタには、実行したい処理を書いた関数を渡し、処理が済んだらresolve関数を呼び出すことで終了を明示する。
- 3.Promiseオブジェクトのthenメソッドに、Promise終了後に処理したい関数を渡す。
- 4.「Promiseの実行が済んだ後にxxxする」
promise.js
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('hello');//実行したい処理
resolve();//実行したい処理が終わったことを明示。thenメソッドへ。
}, 500);
});
promise.then(() => console.log('world!'));
Promise〜値を渡す1〜
- resolveには値を渡すこともできる
- resolve関数に渡した値は、thenメソッドで受け取ることができる。
- 以下サンプル(XHRのレスポンスをresolve関数に渡している)
resolve.js
const promise = new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', 'foo.txt');
xhr.addEventListener('load', (e) => resolve(xhr.responseText));
xhr.send();
});
promise.then((response) => console.log(response));
Promise〜値を渡す2〜
-
reject
はresolve
同様、値を渡すことができる -
reject
が呼ばれたらcatch
が実行されてエラー終了させる
reject.js
const promise = new Promise((resolve, reject) => reject('error'));
promise.then(() => console.log('done')) // thenは実行されない
.catch((e) => console.log(e)); // 「error」とだけ表示される
thenを繋げるthenチェーン
!!!
- これがPromiseのいいところみたいでthenを繋げて書くことができる。
then.js
function printAsync(text, delay) {
const p = new Promise((resolve, reject) => {
setTimeout(() => {
console.log(text);
resolve();
}, delay)
});
return p;
}
printAsync('hello', 500)
//500ミリ秒ごとに「hello」「world」「lorem」「ipsum」と表示する
.then(() => printAsync('world', 500))
.then(() => printAsync('lorem', 500))
.then(() => printAsync('ipsum', 500));
Promiseを使わない時と使った時のソース比較サンプル(ファイルオープン)
- 使わないとき
nothen.js
function openFile(url, onload) {
const xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.addEventListener('load', (e) => onload(e, xhr));
xhr.send();
}
openFile('foo.txt', (event, xhr) => {
openFile('bar.txt', (event, xhr) => {
openFile('baz.txt', (event, xhr) => {
console.log('done!');
});
});
});
- 使った時
then.js
function openFile(url) {
const p = new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.addEventListener('load', (e) => resolve(xhr));
xhr.send();
});
return p;
}
openFile('foo.txt')
.then((xhr) => openFile('bar.txt'))
.then((xhr) => openFile('baz.txt'))
.then((xhr) => console.log('done!'));
複数のPromiseの終了を待つPromise.all
- 上記と同じ処理を書き換えてみる
- 配列で渡してthenは1個で書ける。
- thenの結果は配列で返ってくるため注意
promise.all
function openFile(url) {
const p = new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.addEventListener('load', (e) => resolve(xhr));
xhr.send();
});
return p;
}
const promise = Promise.all([openFile('foo.txt'),
openFile('bar.txt'),
openFile('baz.txt')]);
promise.then((xhrArray) => console.log('done!'))
async関数とawait
-
やっと来ました。これについて知りたくて遡りしてたら結構知ってないといけないことが多かった。。。
-
awaitはPromiseを同期的に展開する(ように見せかける)機能。
-
Promiseオブジェクトを返す関数を呼び出す前につけると取得を待ってから次の処理へ行ってくれる
-
awaitは仕様条件があり、asyncがついた関数の中でしか利用できないと言う条件がある。
-
さっきのファイルオープンの処理をasync/awaitを使って書くと以下のようになる
async.js
function openFile(url) {
const p = new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.addEventListener('load', (e) => resolve(xhr));
xhr.send();
});
return p;
}
async function loadAllFiles() {
//ファイルオープンを待って次の処理へ
const xhr1 = await openFile('foo.txt');
//ファイルオープンを待って次の処理へ
const xhr2 = await openFile('bar.txt');
//ファイルオープンを待って次の処理へ
const xhr3 = await openFile('baz.txt');
console.log('done!');
}
loadAllFiles();
async/awaitのエラー検出
- try-catchが書けるのでその中に書いていく
async.js
function openFile(url) {
const p = new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.addEventListener('load', (e) => resolve(xhr));
xhr.send();
});
return p;
}
async function loadAllFiles() {
try {
//ファイルオープンを待って次の処理へ
const xhr1 = await openFile('foo.txt');
//ファイルオープンを待って次の処理へ
const xhr2 = await openFile('bar.txt');
//ファイルオープンを待って次の処理へ
const xhr3 = await openFile('baz.txt');
console.log('done!');
} catch(error) {
const {
status,
statusText
} = error.response;
console.log(`Error! HTTP Status: ${status} ${statusText}`);
}
}
}
loadAllFiles();