前回でjavascriptはシングルスレッド言語なのにノンブロッキング処理される理由を説明しました。
言い換えるとjavascriptは同期の言語なのにも非同期的な処理ができるということです。
今回は非同期処理するためコールバック関数を連続で使う時発生する問題とその解決案の一つについてお話します。
$.get('url', function(response) {
parseValue(response, function(id) {
auth(id, function(result) {
display(result, function(text) {
console.log(text);
});
});
});
});
例えば上記のようにデータを持って来てエンコーディング・認証などを処理して、画面へ表示しようとします。
この全処理を非同期でしないといけない場合、コールバックの中にコールバックを繰り返しないと行けないです。
この構造は読みにくいし、何かの変更の対応も難しくなります。
もちろんコードを変えて下記のようにすれば行けますが。
function parseValueDone(id) {
auth(id, authDone);
}
function authDone(result) {
display(result, displayDone);
}
function displayDone(text) {
console.log(text);
}
$.get('url', function(response) {
parseValue(response, parseValueDone);
});
上記のように、非同期処理するためコールバック関数を連続で使う時、発生する問題を簡単に解決するため登場したのがpromiseです。
Promiseとは?
Promise インターフェイスは作成時点では分からなくてもよい値へのプロキシです。
Promise を用いることで、非同期アクションの成功や失敗に対するハンドラーを関連付けることができます。
これにより、非同期メソッドは、最終的な値を返すのではなく、未来のある時点で値を持つ Promise を返すことで、
同期メソッドと同じように値を返すことができるようになります。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise
・Promiseオブジェクトを、Promise
コンストラクタで生成
→ コンストラクタの引数にexecutorという関数を使って、
executor関数はresolveとrejectという引数を使います。
※ resolveとrejectは関数です。
→ 生成するとpending状態になります。
・resolve()を実行するとfulfilled状態になります。
→ 完了
new Promise((resolve, reject) => {
//
// ...
resolve(); // fulfulled状態
});
・reject()を実行するとreject状態になります。
→ 失敗、またはエラー
new Promise((resolve, reject) => {
//
// ...
reject(); // reject状態
});
・.then / .catch
→ promiseオブジェクトがfulfilledになった場合、実行するcallback関数
→ promiseオブジェクトがrejectになった場合、.catch APIを使って、
エラーハンドリングできます。
・.finally
→ プロミスにハンドラーを付加し、元のプロミスが解決されたときに解決される新しいプロミスを返します。このハンドラーは、成功か失敗かに関わらず、元のプロミスが完了したときに呼ばれます。
/*
* pというpromiseオブジェクトがfullfilledになった時、p.thenのcallback関数が実行する
*/
{
function p() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('hello')
}, 4000);
});
}
p()
.then(message => {
console.log('4秒後fulfilledになります。', message);
}).catch(() => {
console.log('4秒後rejectedになります。');
}).finally(() => {
console.log('処理完了');
});
}
サンプル)
各2秒毎で、指定した関数がが実行して、5になった場合、エラーメッセージ出力
function increase(number) {
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
const result = number + 1;
if(result == 5) {
const e = new Error('数字オーバー');
return reject(e);
}
resolve(result);
}, 2000);
});
return promise;
}
increase(0).then(number => {
console.log(number);
return increase(number);
}).then(number => {
console.log(number);
return increase(number);
}).then(number => {
console.log(number);
return increase(number);
}).then(number => {
console.log(number);
return increase(number);
}).then(number => {
console.log(number);
return increase(number);
}).then(number => {
console.log(number);
return increase(number);
}).then(number => {
console.log(number);
return increase(number);
}).catch(e => {
console.log(e.message);
})
おわりに
Promiseは複雑な非同期処理を読みやすく書けます。
また、エラーの際、定義したエラーメッセージから補修もしやすいです。