このページの内容
Promiseとは
Promiseは SPA(シングルページアプリケーション)を構築するうえではもはや欠かせない、非同期処理を制御するためのオブジェクトです。
- ある非同期処理が完了するのを待って、次の処理を動作させる
- 「ある非同期処理が完了するのを待って、次の処理を動作させる」を複数並行で動作させる
- 複数の非同期処理を並行で動作させ、すべて完了するのを待って、次の処理を動作させる
- 複数の非同期処理を直列で動作させる
ある非同期処理が完了するのを待って、次の処理を動作させる
サンプルの非同期処理として、下記のような関数 asyncMethod() を準備しました。
これは引数で指定されたミリ秒だけ待機したのち、文字列 id + 'finished' を解決値とするという動作をします。
この関数は、実際のシステムではサーバー上のリソースを取得するなどの処理に該当します。
function asyncMethod(id, waitTimeMillis) {
return new Promise(function(resolve) {
setTimeout(function(){
resolve(id + ' finished');
}, waitTimeMillis);
});
}
この関数を呼び出すための関数 main1() を準備します。
function main1() {
console.info('main start');
asyncMethod('A', 3000).then(
function(result){
console.info(result);
}
);
console.info('main end');
}
main1()を呼び出してみると、開発者ツールのコンソールに下記のように表示されるはずです。
main start main end A finished
解説
main1() は、まずは console.info('main start') を処理します。
次に asyncMethod() を呼び出します。ここまでは特別なことは何もありません。
ただ、その後、その処理が完了するのを待たず、次のステップを処理します。これが非同期処理です。
ここで言う次のステップとは、then() の中ではなく、console.info('main end') です。
そして 3000ms 後にasyncMethod が解決されて、console.info(id + ' finished') が処理されます。
「ある非同期処理が完了するのを待って、次の処理を動作させる」を複数並行で動作させる
パラメータが配列に格納されているケースを考えてみます。
Array.prototype.forEach() を使って、ループ処理します。
functin main2() {
console.info('main start');
var promiseArray = [];
var params = [
['A', 2000],
['B', 3000],
['C', 1000]
];
params.forEach(function(param, index){
asyncMethod(param[0], param[1]).then(function(){
console.info(result);
});
});
console.info('main end');
}
main2()を呼び出してみると、下記のように表示されます。
main start main end C finished A finished B finished
よくハマる間違った実装
ここで単純に for ループのループカウンタ i を使って、下記のように実装してしまうと、意図した結果が得られないので注意が必要です。
functin main3() {
console.info('main start');
var params = [
['A', 2000],
['B', 3000],
['C', 1000]
];
for (var i = 0; i < params.length; i++) {
var param = params[i];
asyncMethod(param[0], param[1]).then(function(result){
// then の外側の変数を参照すると、意図しない値が入ることがある
console.info(param[0] + ' finished.');
});
});
console.info('main end');
}
main3() が回しているループカウンタと、asyncMethod()が参照しているループカウンタは、非同期処理のため一致するとは限りません。その結果、下記のように表示されてしまいます。
main start main end C finished C finished C finished
複数の非同期処理を並行で動作させ、すべて完了するのを待って、次の処理を動作させる
Promise.all を使って、下記のように実装します。
function main4() {
console.info('main start');
var promiseArray = [];
var params = [
['A', 2000],
['B', 3000],
['C', 1000]
];
params.forEach(function(param, index){
promiseArray.push(
asyncMethod(param[0], param[1]).then(function(result){
console.info(result);
}));
});
Promise.all(promiseArray).then(
function(resultArray){
console.info('all done');
}
);
console.info('main end');
}
main start main end C finished A finished B finished all done
もっとシンプルに
これは、Array.prototype.map() を使って、下記のように短く書くことができます。
function main5() {
console.info('main start');
var params = [
['A', 2000],
['B', 3000],
['C', 1000]
];
Promise.all(
params.map(function(param){
return asyncMethod(param[0], param[1]).then(function(result){
console.info(result);
});
})
).then(
function(resultArray){
console.info('all done');
}
);
console.info('main end');
}
複数の非同期処理を直列で動作させる
「1つ目が終わるのを待って2つ目を起動し、2つ目が終わるのを待って3つ目を起動する」といったことを繰り返す方法です。
function main6() {
console.info('main start');
var promise = Promise.resolve();
var params = [
['A', 2000],
['B', 3000],
['C', 1000]
];
params.forEach(function(param, index){
promise = promise.then(function(){
return asyncMethod(param[0], param[1]).then(function(result){
console.info(result);
});
});
});
promise.then(function(){
console.info('all done');
});
console.info('main end');
}
これを実行すると、下記のように表示されます。
main start main end A finished B finished C finished all done