malcommac/Hydra コードレビュー2 (then)
概要
- 前回HydraのPromiseについてコードを読んでいった
- 今回はPromiseを使う上で無視できないthenを深掘りしていく
then
Hydra/Promise+Then.swift at master · malcommac/Hydra · GitHub
定義を見ていく
@discardableResult public func then<N>( in context: Context? = nil, _ body: @escaping ( (Value) throws -> N) ) -> Promise<N> @discardableResult public func then<N>( in context: Context? = nil, _ body: @escaping ( (Value) throws -> (Promise<N>) )) -> Promise<N> @discardableResult public func then( in context: Context? = nil, _ body: @escaping ( (Value) throws -> () ) ) -> Promise<Value>
上の定義を見るとthenの呼び出し方は3種類あり、以下のように捉えられる
- 現在のPromiseを発火させた結果、解決したValueを元に
- 指定した型の値(
N
)として後続のPromiseに繋げるValue -> N
- 指定した型のPromise(
Promise<N>
)として後続に繋げるValue -> Promise<N>
- 現在のPromiseの型(
Promise<Value>
)として後続に繋げるValue->Void
- 指定した型の値(
そして@discardableResult
のattributeが付いているため代入操作をしなくても
// some: Promise<Some> としたとき some.then { _ in }
この書き方でPromiseを発火させることができる
実装を見ていく
let p = Promise<Bool> { resolve, _, _ in resolve(true) }
Promise<Bool>
型のインスタンスとして、ただtrueをresolveするPromise p
を生成する
そしてpに対して
then (in context: Context? = nil, _ body: @escaping ( (Value) throws -> () ) ) -> Promise<Value>
上記thenを実行する
p.then { (ret) in print("[then end]: \(ret)") }
そうすると関数内部でnextPromiseという変数が生成される
let nextPromise = Promise<Value>(in: ctx, token: self.invalidationToken, { resolve, reject, operation in let onResolve = Observer.onResolve(ctx, { value in do { try body(value) resolve(value) } catch let error { reject(error) } }) let onReject = Observer.onReject(ctx, reject) let onCancel = Observer.onCancel(ctx, operation.cancel) self.add(observers: onResolve, onReject, onCancel) })
このnextPromiseは、
- bodyとして、onResolve / onReject / onCancelのObserver型のインスタンスを生成
- それらのObserver群はthenをcallしたPromiseのobserversに追加
- 元のPromise
p
がresolved状態になるタイミングでnextPromiseのonResolveが発動 - thenの引数として渡したbodyが発火しその結果resolve、もしくはrejectが呼ばれる
という動きになる、そして p
と nextPromise
がいつ発火するのかという話になるのだが、
thenが呼ばれたタイミングでこの2つのPromiseのrunBodyを実行する
nextPromise.runBody() self.runBody() return nextPromise
こうすることで元の p
のresolve(true)が呼ばれresolved状態からのnextPromiseの発火により
チェインしてるように見せることができる
あとは非同期で連結で実行するための定義tipsとして単純な実行、map、flatMapのように扱うために3種類のパターンで
オーバーロードで定義されているのは使い勝手としても良さそうである
public func then( in context: Context? = nil, _ body: @escaping ( (Value) throws -> () ) ) -> Void
のように用意せず @discardableResult
を使うことでシンプルなインターフェースになってると言えそう
終わりに
今回はPromiseのthenのみについて深掘りをした、自分で非同期ライブラリを実装するような場合に参考になるレビューだったように思う
次回はcatchを深掘りしていく予定