コールバック関数のネスト
関数を順番に呼び出したい時、コールバック関数をネスト出来ます。
以下は、例です。
【コールバック関数のネスト例】
<script>
function func1(callback) {
alert("func1完了");
callback();
}
function func2(callback) {
alert("func2完了");
callback();
}
function func3() {
alert("func3完了");
}
func1(function() {
func2(function() {
func1(function() {
func2(func3);
});
});
});
</script>
上記はfunc1→func2→func1→func2→func3の順番で呼び出されますが、赤字の所が分かり辛いと思います。これに引数や変数等が加わってくると、記述していても間違いやすくなります。
このようにコールバック関数は、簡単に次の関数を指定出来るメリットがありますが、ネストすると分かり辛いデメリットがあります。
Promise.resolve
先ほどの例をPromiseを使って書き換えると、以下になります。
【Promise.resolveの利用例】
<script>
function func1() {
alert("func1完了");
}
function func2() {
alert("func2完了");
}
function func3() {
alert("func3完了");
}
Promise.resolve()
.then(func1)
.then(func2)
.then(func1)
.then(func2)
.then(func3);
</script>
赤字が書き換えた部分ですが、非常に分かり易くなったと思います。Promiseがオブジェクトで、resolveやthenがメソッドです。func1等は、関数宣言せずに.then(function() {alert("func1完了");})のように無名の関数式で記述する事も出来ます。
このように、複数の関数を呼び出す必要がある時はPromiseを使うと便利ですが、InternetExplorerではサポートされていません。
チェーン
引数は、以下のようにして渡せます。
【Promise.resolveでの引数利用例】
<script>
function func1(x) {
alert(x);
return "func2完了";
}
function func2(y) {
alert(y);
}
Promise.resolve("func1完了")
.then(func1)
.then(func2);
</script>
赤字部分で「func1完了」がfunc1に渡されます。青字部分で「func2完了」がfunc2に渡されます。このように、次のthenに引数を渡す事をチェーンと呼びます。
もし、チェーンで渡したくない場合、青字部分を削除し、.then(func2)を.then(function() {func2("func2完了");})に書き換えると引数として渡せます。
エグゼキューター関数
Promiseは、成功と失敗で処理を分けて記述出来ます。
【Promiseの利用例】
<script>
function func1(resolve, reject){
x = Math.ceil( Math.random() * 2);
if(x == 1){
resolve("成功");
} else {
reject("失敗");
}
}
function func2(z){
alert(z);
}
var promise1 = new Promise(func1);
promise1
.then(func2)
.catch(func2);
</script>
赤字のresolveで引数に"成功"を渡し、thenで成功時の処理を行っています。青字のrejectで引数に"失敗"を渡し、catchで失敗時の処理を行っています。このため、ランダムなxが1であれば「成功」、それ以外は「失敗」と表示されます。
func1はエグゼキューター関数と呼ばれ、resolve(成功)とreject(失敗)の値を返し、thenとcatchで引数として使えます。
尚、catchの代わりにthenで.then(func2,func2)と記述しても同じです。第一パラメタが成功時、第二パラメタが失敗時の処理を示します。
非同期処理との組み合わせ
「sleep処理」でご紹介したsetTimeoutと組み合わせて利用する場合の例は、以下の通りです。
【PromiseとsetTimeoutの組み合わせ例】
<script>
function func1(x){
return new Promise(function(resolve,reject){
setTimeout(function(){resolve();},x);
});
}
function func2() {
alert("5秒経ちました");
}
Promise.resolve(5000)
.then(func1)
.then(func2);
</script>
赤字部分でプロミスの成功時だけを扱っています。func1が実行され、xが5000にセットされるため、青字部分のsetTimeoutでは5秒待つ事になります。5秒後にresolve();が実行されますが、これが成功を示します。returnがあるため、成功を示す応答があるまで待ち、次のthenでfunc2が処理されます。つまり、5秒後にfunc2が実行出来ます。
次は、addEventListenerの例です。
【PromiseとaddEventListenerの組み合わせ例】
<div>
<image src="image/trump/11.png" alt="スペードA" id="image1">
</div>
<script>
function func1() {
alert("クリックされました");
}
function func2() {
alert("スペードのAです");
}
var x = document.getElementById("image1");
var promise1 = function() {Promise.resolve()
.then(func1)
.then(func2);
};
x.addEventListener("click", promise1, false);
</script>
赤字部分で処理を記述し、青字部分でイベントリスナーのハンドラとして登録しています。このため、画像をクリックすると「クリックされました」「スペードのAです」の順に表示されます。thenで呼び出す関数を変える事で、異なる処理をさせる事も可能です。
まとめ
非同期処理で複数関数を処理すると、順番が分かり辛くなりがちです。
Promiseを使うと、.then(func1)、.then(func2)等、非同期処理の順番を分かり易く記述出来ます。又、チェーンによって、簡単に次の処理に値を渡す事が出来ます。