11) Promiseオブジェクトのライブラリ「Q」の紹介
ES6 Promise はごくシンプルな機能しか持っていません。非同期処理を随所で使おうとすると物足りなくなる事もあるでしょう。そんな時はライブラリの力を借りましょう。
kriskowal/q
https://github.com/kriskowal/q
Promiseオブジェクトを提供するライブラリはたくさんありますが Q は最もメジャーなライブラリの一つです。CommonJSのサイトではPromises/Aを実装しているライブラリとしてトップに名前が上げられています。
Promises/A
http://wiki.commonjs.org/wiki/Promises/A
基本的にはES6 Promiseと同じ
Q( )
Qのコンストラクタ関数を使うと値をPromiseオブジェクトに変換する事ができます。
// fulfilledな状態のPromiseオブジェクトになる
Q(1).then(function(value){
console.info(value);
});
この方法では他のライブラリの返すPromiseオブジェクトを Q のPromiseオブジェクトに変換する事ができます。ライブラリにより異なる挙動のPromiseオブジェクトを返す事がありますがQ()
でラップする事により統一性を持たせる事ができます。
Q($.ajax()).then()
static method
ES6 Promise と同じstatic methodも提供されています。
// fulfilledな状態のPromiseオブジェクトを生成
Q.resolve(1);
// rejectedな状態のPromiseオブジェクトを生成
Q.reject(new Error('abort'));
then()
Q のPromiseオブジェクトは Thenable です。ES6 Promise と同じようにチェインする事ができます。
Q.resolve(1)
.then()
.then()
;
catch()
catch()
は rejected な状態を拾います。これも ES6 Promise と同じですね。
Q.resolve(1)
.then()
.then()
.catch(function(){...})
;
then()
のチェイン中に発生したエラーを拾うのも ES6 Promise と同じです。
Q.resolve(1)
.then(function(){
throw new Error('unexpected');
})
.then()
.catch(function(error){
console.log(error.message);
})
;
基本の機能をサポート
fin()
fulfilled rejected どちらの状態でも最後に必ず呼ばれます。(正式なメソッド名はfinally()
ですが予約語と重なるため、こちらはモダンブラウザでないと使用する事ができません)
task().fin(function(){...})
done()
処理されなかった rejected をエラーに変えます。
Q.resolve(1)
.then(function(){
return Q.reject(new Error('abort'));
})
.then()
.done()
;
// ブラウザのエラー
// Uncaught Error: abort
when()
when()
はstaticにthen()
を呼び出すメソッドです。
Q.when('initial value', function(){...})
.then(function(){...})
.then(function(){...})
when()
を使うと逐次処理を行うreduce()
がスーパーコンパクトになります。
tasks.reduce(Q.when, Q('initial value'));
複数のPromiseオブジェクトを扱う
all()
ES6 Promise と同じall()
も用意されています。
Q.all(tasks)
.then()
.catch()
;
allSettled()
Promiseオブジェクトを配列で渡し全て実行します。
all()
とよく似た動作ですが、all()
はどれか一つでも rejected な状態になると処理を中断するのに対し、allSettled()
は処理を中断しません。
コールバック関数に渡される引数はオブジェクト配列になっており、それぞれのtask
プロパティを参照する事で fulfilled および rejected を判断する事ができます。
Q.allSettled([taskA(), taskB()])
.then(function(tasks){
tasks.forEach(function(task){
if (task.state === 'fulfilled') {
console.info(task.value);
} else {
console.log(task.reason);
}
})
})
;
spread()
then()
の代わりに使うと、all()
の結果を配列でなく引数のリストで受け取る事ができます。
Q.all([taskA(), taskB()])
.spread(function(taskA, taskB){
console.info(taskA, taskB);
})
;
同期処理との連携
fcall()
同期処理の関数をPromiseオブジェクトとして扱います。 値を返す場合は fulfilled になります。
Q.fcall(func,[arg]).then(...);
関数内でエラーが発生した場合は rejected として扱います。
その他
progress()
fulfilled rejected の状態になる前に進捗を知る事ができます。
var promise = Q.Promise(function(resolve, reject, notify){
setTimeout(function(){notify(1);}, 1000);
setTimeout(function(){notify(2);}, 2000);
setTimeout(function(){resolve();}, 3000);
});
promise
.then(function(){
// 3) 'resolved'
console.info('resolved');
})
.progress(function(value){
// 1) '1'
// 2) '2'
console.log(value);
})
;
まとめ
Qの良いところは「こんなメソッドがあったら便利だな」という細かなメソッドがたくさん提供されているところだと思います。公式ドキュメントが長いので最初に見た時は面食らうかもしれませんが、少しずつ読んでみるとPromiseオブジェクトのシンプルな動作を邪魔せずに様々な側面からサポートするメソッドを提供しているのだと分かります。
興味を持った方は、ぜひ公式ドキュメントをご覧ください。
q
http://documentup.com/kriskowal/q/