ES6を使う機会がありそうで、Promiseについて全然知らなかったので、実際に書きながら勉強してみたときのメモ。
なお、以下を参考にさせて頂きました。
0から勉強する時でもとても分かりやすかったです。
環境
- Node.js v4.2.2
Promiseとは
- 非同期処理を操作できる
- 非同期処理の成功時(resolve)、失敗時(reject)の処理を明示的に書くことが出来る
- 非同期処理を平行・直列に実行させることが出来る
とりあえずやってみる
とりあえず、参考にさせて頂いたサイトのコードを書いてみました。
function asyncFunction() {
// Promiseオブジェクトを返却する.処理成功時にはresolveが呼ばれる
return new Promise(function (resolve, reject) {
setTimeout(function () {
// 成功
resolve('Async Hello world');
}, 16);
});
}
asyncFunction().then(function (value) {
// 非同期処理成功
console.log(value); // => 'Async Hello world'
}).catch(function (error) {
// 非同期処理失敗。呼ばれない
console.log(error);
});
上記では非同期で実行されるsetTimeout
メソッドをPromiseを使って実行しています。
resolve
メソッドを呼びだす事で非同期処理の成功を示し、then
メソッドの引数の処理を実行しています。
以下はにreject
関数を呼び出すことで非同期処理の失敗を示し、catch
メソッドの引数の処理を実行しています。
function asyncFunction() {
return new Promise(function (resolve, reject) {
setTimeout(function () {
// 失敗
reject(new Error('Error'));
});
});
}
asyncFunction().then(function (value) {
console.log(value);
}).catch(function (error) {
// 呼ばれる
console.log(error); // => 'Error'
});
メソッドチェーンについて理解する
function taskA () {
console.log("TaskA");
}
function taskB () {
console.log("TaskB");
}
function onRejected(error) {
console.log("error = " + error);
}
var promise = Promise.resolve();
promise
.then(taskA)
.then(taskB)
.catch(onRejectted);
上記を実行するとtaskA,taskB
と順番に標準出力が表示されます。
- var promiseのresolve()が実行され
- then(taskA)が実行され
- then(taskB)が実行された
という流れだと思います。
なお、上記のように記載した場合、taskAもしくはtaskBのいずれかでエラーが発生した場合にonRejected
メソッドが呼び出されます。
エラーが発生したとは具体的に以下のいずれかの場合となります。
- 例外(thrown new Error)が発生した
- rejectが呼び出された
function taskA () {
throw new Error('Error');
console.log("TaskA");
}
function taskB () {
console.log("TaskB");
}
function onRejectted(error) {
console.log("error = " + error);
}
var promise = Promise.resolve();
promise
.then(taskA)
.then(taskB)
.catch(onRejectted);
上記実行するとerror = Error: Error
のみ標準出力されます。
taskBの関数については呼び出されません。
非同期処理を並列で行う
一つの非同期処理をPromiseにしても嬉しくないので、並行で非同期処理を行う方法を確認します。
(taskAが終わってtaskBをやって。。。というような逐次処理は次の章で)
Promise.all()
Promise.all()
メソッドはPromiseオブジェクトの配列を受け取り、全てのPromiseオブジェクトがresolveされたタイミングでthen
が呼び出されます。
書いて確認してみましょう。
var taskA = new Promise(function(resolve, reject) {
setTimeout(function () {
console.log('taskA');
resolve();
}, 16);
});
var taskB = new Promise(function(resolve, reject) {
setTimeout(function () {
console.log('taskB');
resolve();
}, 10);
});
var before = new Date();
Promise.all([taskA, taskB]).then(function () {
var after = new Date();
var result = after.getTime() - before.getTime();
console.log(result);
});
上記の結果は以下のようになります。
taskB
taskA
16
上記より以下であることが分かります。
- taskAとtaskBは並行で実行されている(直列で実行されている場合には差分の秒数として26が表示されるはず)
- taskAとtaskBが終わってからthenが呼び出されている
ちなみにrejectを返却するとどうなるのかやってみます。
var taskA = new Promise(function(resolve, reject) {
setTimeout(function () {
console.log('taskA');
resolve();
}, 16);
});
var taskB = new Promise(function(resolve, reject) {
setTimeout(function () {
console.log('taskB');
reject();
}, 10);
});
var before = new Date();
Promise.all([taskA, taskB]).then(function () {
var after = new Date();
var result = after.getTime() - before.getTime();
console.log(result);
}).catch(function () {
console.log('error');
});
結果は以下です。
taskB
error
taskA
上記より以下であることが分かります。
- 配列に指定されたいずれかのPromiseでrejectが呼び出されたタイミングで
catch
メソッドが呼びされ、実行される - catchメソッドが呼びされても終了していないPromiseの処理は継続される
- 全てのPromiseの処理が終わっても
then
メソッドは呼び出されない
Promiseのいずれかでもエラーになった時点で他のPromiseの処理を待たずに終了させたい場合にはprocess.exit(1)
など使えば良さそうですね。
Promise.race()
Promise.all()は全てのPromiseが呼び出された後にthenメソッドが呼び出されました。(catchではなく)
Promise.race()は一つでもresolve, rejectが呼び出されたら、then
もしくはcatch
が呼びされます。
確認します。
var taskA = new Promise(function(resolve, reject) {
setTimeout(function () {
console.log('taskA');
resolve();
}, 16);
});
var taskB = new Promise(function(resolve, reject) {
setTimeout(function () {
console.log('taskB');
resolve();
}, 1);
});
var before = new Date();
Promise.race([taskA, taskB]).then(function () {
var after = new Date();
var result = after.getTime() - before.getTime();
console.log(result);
}).catch(function () {
console.log('error');
});
結果
taskB
5
taskA
1という表示を期待したのですが、msなので多少は仕方なさそうですが、以下の結果が分かりました。
- いずれかのPromiseのresolveが呼びされたタイミングで
then
メソッドが実行される - thenメソッドが呼びされた後もまだ終了していないPromiseの処理は継続
こちらもrejectが呼び出された場合について確認します。
var taskA = new Promise(function(resolve, reject) {
setTimeout(function () {
console.log('taskA');
resolve();
}, 16);
});
var taskB = new Promise(function(resolve, reject) {
setTimeout(function () {
console.log('taskB');
reject();
}, 1);
});
var before = new Date();
Promise.race([taskA, taskB]).then(function () {
var after = new Date();
var result = after.getTime() - before.getTime();
console.log(result);
}).catch(function () {
console.log('error');
});
結果は以下。
Promise.all()と同じ挙動ですね。
taskB
error
taskA
参考ページにも書いてありましたが、Promiseには処理を途中でキャンセルというのはないみたいですね。
非同期処理を逐次処理で実行する
Promise.all()及びPromise.race()は並行で非同期処理を制御できるメソッドでした。
次に逐次処理(直列処理)について確認します。
まずは、参考サイトを参考にしてシンプルに書いてみます。
ECMAScript6のアロー関数とPromiseまとめ - JavaScript
Promise.resolve()
.then(function () {
return new Promise(function(resolve, reject) {
setTimeout(function () {
console.log('taskA');
resolve('taskA death');
}, 16);
});
})
.then(function(value) {
return new Promise(function(resolve, reject) {
setTimeout(function () {
console.log(value);
console.log('taskB');
resolve('taskB death');
}, 1);
});
})
.then(function (value) {
console.log('then');
console.log(value);
}).catch(function (error) {
console.log(error);
});
シンプルですね。
下記参照させて頂いたページを参考に並列処理の際に利用した関数を逐次処理させます。こちらは関数に分かれているのでテストもしやすそうです。
function sequenceTasks(tasks) {
function recordValue(results, value) {
results.push(value);
return results;
}
var pushValue = recordValue.bind(null, []);
return tasks.reduce(function (promise, task) {
return promise.then(task).then(pushValue);
}, Promise.resolve());
}
var promises = {
doTaskA: function() {
return taskA().then();
},
doTaskB: function() {
return taskB(value).then();
}
};
function taskA() {
return new Promise(function(resolve, reject) {
setTimeout(function () {
console.log('taskA');
resolve('taskA death');
}, 16);
});
}
function taskB(value) {
return new Promise(function(resolve, reject) {
setTimeout(function () {
console.log(value);
console.log('taskB');
resolve('taskB death');
}, 1);
});
}
function main() {
return sequenceTasks([promises.doTaskA, promises.doTaskB]);
}
main().then(function(value) {
console.log('then');
console.log(value);
// taskAもしくはtaskBでエラーの場合に呼び出される
}).catch(function(error) {
console.log(error);
});
sequenceTasks
メソッドを使うとPromiseでの実行結果がそれぞれpushされるので最終的に後から結果の取得を行うこともできます。
taskA
[ 'taskA death' ]
taskB
then
[ 'taskA death', 'taskB death' ]
以下が確認できました。
- 逐次(直列)で非同期処理を行える
- 1つ目の非同期処理の結果を2つ目の非同期処理に渡すことが出来る
- taskAもしくはtaskBでエラーがあった場合の処理を1か所に書くことが出来る