SwiftTaskは良い感じのPromiseライブラリです。
ですがjQuery.Deferredと同じ感覚でいるとハマったり、ちょっと突っ込んだ使い方をしようとすると試行錯誤が必要だったりしたのでTipsをまとめました。
ほかに見つけましたら追加します。
以下はすべてバージョン4.1.0時点での情報です。
値だけを返すTaskは作れる!
問答無用でfulfill/rejectするTaskを作りたいという場合、
以下のように値をfulfill/rejectするだけのクロージャでinitしてもいいのですが・・・
// still ok but not so cool
let successTask = Task<Void, String, Int> { fulfill, _ in fulfill("succeeded!") }
let failureTask = Task<Void, String, Int> { _, reject in reject(-1) }
簡単なイニシャライザがあります。
let successTask = Task<Void, String, Int>(value: "succeeded!")
let failureTask = Task<Void, String, Int>(error: -1)
errorの値をビックリマークしても良いのか
良く訓練されたSwifterはハテナマークを見ると瞬時に戦闘態勢になると聞きます。
Task#failureに渡すfailureClosureの引数は(error:Error?, isCancelled:Bool)
のようになっていますが、このerrorがnilになることはあるのでしょうか。
私の理解では、nilになるのは以下の場合です:
- 引数なしでcancelされた場合はnilになる
- 複数のTaskをTask.anyでまとめたTaskがfailした場合(つまりまとめたすべてのTaskがfailした場合)はnilになる
- .successでTaskを直列に繋げた際にエラーの型が一致しない場合
3については少し解説が必要です。
Task#successでは、以下のように任意のTaskを直列につなげることができます。
しかし実際には、ここでErrorの型まで変えてしまうとエラー時の値を正しく受け取れない(errorがnilになる)という実装上の制約があります。
Task<Void, String, NSError>(value: "ok").succes { (result) -> Task<Int, Double, String> in
return Task<Int, Double, String>(error: "error!")
}.failure { (errorOpt, isCancelled) -> Double in
print(errorOpt) // ここでnilが出力される
return -1.0
}
なお、Task#thenを使えばそのようなことはありませんので、Errorの型も変えたい場合はこちらを使用するといいと思います。
Task<Void, String, Int>(value: "ok").then{ (valueOpt, errorInfoOpt) -> Task<Void, Int, String> in
return Task<Void, Int, String>(error: "error")
}.failure { (errorOpt, isCancelled) -> Int in
print(errorOpt) // ここでは「error」と出力される
return -1
}
BulkProgressをめぐる冒険
Task#successやTask#thenで直列につなぐTaskとしてTask.allやTask.someでまとめたタスクを指定したい場合の話です。
まず以下のコードを書きました。
let allTasks = Task.all([
Task<Int, String, NSError>(value: "value1"),
Task<Int, String, NSError>(value: "value2"),
Task<Int, String, NSError>(value: "value3")
])
allTaskの型を調べるとTask<BulkProgress, [String], NSError>
となるので、なるほど、と以下の様なコードを書きます。
するとBulkProgressなんて型はないと怒られます。
Task<Int, String, NSError>(value: "ok").success { (value) -> Task<BulkProgress, [String], NSError> in
// 「Use of undeclared type 'BulkProgress'」と怒られる
let allTasks = Task.all([
Task<Int, String, NSError>(value: "value1"),
Task<Int, String, NSError>(value: "value2"),
Task<Int, String, NSError>(value: "value3")
])
return allTasks
}
いろいろ調べた結果、「まとめたTaskの型.BulkProgress」を指定すると良いようです。
typealias BulkTask = Task<Int, String, NSError>
Task<Void, String, NSError>(value: "ok").success { (value) -> Task<BulkTask.BulkProgress, [String], NSError> in
let allTasks = Task.all([
BulkTask(value: "value1"),
BulkTask(value: "value2"),
BulkTask(value: "value3")
])
return allTasks
}
4.1.0でTask#on(success:, failure:)が追加されました
タスク失敗時の処理を記述したい!となったときにはTask#failureを使おうと思うのが人情だと思うのですが、
Task#failureに指定するfailureClosureの返り値の型はValue
、つまり成功時に期待するデータの型でなければなりません。
IntやString程度なら-1だの""だのを返しておけばよいのですが、そのようなテキトーなインスタンスの生成が面倒なクラスだったりすると心が折れてしまいます。
4.1.0で追加されたTask#onは、純粋なイベントハンドラとして使えるメソッドです。
Task<Int, String, NSError>(value: "ok").on(success: { (result) -> Void in
print("succeeded!")
}, failure: { (errorOpt, isCancelled) -> Void in
print("failed!")
})
Task#successやTask#failureのように値や型の変換/タスクの連結といった機能がなく、そのぶん気軽にTaskの結果に応じた処理を書くことができます。
以上です。
またなにか見つけたら追記します。
SwiftTaskは現状Swiftでまともに使える&書ける唯一のPromiseライブラリだと思います。
作者のinamiyさん、ありがとうございます!