昨日の Promise/A+ のトレース

昨日の Ruby に書き直した Promise/A+ 実装がどのように、Promise のインスタンスと Handler のインスタンスをつなげて、Promise インスタンスが状態変化していくかをトレースしてみました。

resolve の後に _then する場合

prom0 = Promise.new                  # (1)
prom0.resolve(42)                    # (2)
prom1 = prom0._then{|x| p x; 88 }    # (3)

(1) の直後。ペンディング状態の Promise ができ、prom0 を束縛してあります。

prom0=#<Promise @state=:pending @value=nil @deferred=nil>

(2) の直後。prom0 は :resolved 状態に変化して、値が 42 に更新されます。

prom0=#<Promise @state=:resolved @value=42 @deferred=nil>

(3) の直後。prom0 に変化なし。_then が :resolved 状態の新しい Promise を返します。

prom0=#<Promise @state=:resolved @value=42 @deferred=nil>
prom1=#<Promise @state=:resolved @value=88 @deferred=nil>

(2) から (3) に至る途中経過では、_then が一時的に作った Handler 構造体を handle メソッドに渡しています。このときの、handle のレシーバは prom0 です。

(prom0=#<Promise @state=:resolved @value=42 @deferred=nil>).handle(
  #<Handler onresolved=Proc{|x| p x; 88 } onrejected=nil
    promise=prom1=#<Promise @state=:pending @value=nil @deferred=nil>>)

レシーバの prom0 はペンディング状態でないので、onresolved コールバックに prom0 の @value を渡して、戻り値を得ます。得た戻り値は、即座に handle.promise の resolve に使われます。

ret = handler.onresolved.call(42)   #=> 88
handler.promise.resolve(ret)        #=> nil

prom0.handler では、self が :resolved なので、_then コールバックを直ちに呼び出します。

self=prom0=#<Promise @state=:resolved @value=42 @deferred=nil>
handler=#<Handler onresolved=Proc{|x| p x; 88 } onrejected=nil
          promise=prom1=#<Promise @state=:pending @value=nil @deferred=nil>>)

実引数は self の @value、つまり 42 です。すると、88 が戻ってきます。この戻り値を実引数にして、handler.promise を resolv(88) します。

ret=self.handler.onresolved(42);
self.handler.promise.resolv(ret);

resolv の結果、handler.promise も :resolved 状態に変化して、値に 88 がセットされます。この Promise の @deferred は nil なので、これ以上おこなうことはありません。

self=prom0=#<Promise @state=:resolved @value=42 @deferred=nil>
handler=#<Handler onresolved=Proc{|x| p x; 88 } onrejected=nil
          promise=prom1=#<Promise @state=:resolved @value=88 @deferred=nil>>)

_then に戻って、:resolved に変化済みの新しい Promise が返ります。

_then の後に resolve する場合

prom0 = Promise.new                 # (1)
prom1 = prom0._then{|x| p x; 88 }   # (2)
prom0.resolve(42)                   # (3)

(1) の直後。ペンディング状態の Promise ができています。

prom0=#<Promise @state=:pending @value=nil @deferred=nil>

(2) の直後。_then コールバックと新しい Promise が元の Promise の @deferred につながります。この時点でも、両方の Promise はペンディング状態です。

prom0=#<Promise @state=:pending @value=nil
        @deferred=#<Handler onresolved=Proc{|x| p x; 88 } onrejected=nil
          promise=prom1>>
prom1=#<Promise @state=:pending @value=nil @deferred=nil>

(3) の直後。両方の Promise の状態が :resolved へ変化し、それぞれ値がセットされます。

prom0=#<Promise @state=:resolved @value=42
        @deferred=#<Handler @onresolved=Proc{|x| p x; 88 } @onrejected=nil
          @promise=prom1>>
prom1=#<Promise @state=:resolved @value=88 @deferred=nil>

prom0.resolve(42) によって (2) から (3) に至る途中経過も見てみます。

(3-1) prom0 の resolve メソッドで prom0 の状態が :resolved に 値が 42 に変わります。

prom0=#<Promise @state=:resolved @value=42
        @deferred=#<Handler @onresolved=Proc{|x| p x; 88 } @onrejected=nil
          @promise=prom1>>
prom1=#<Promise @state=:pending @value=nil @deferred=nil>

(3-2) prom0 の @deferred が nil でないので、prom0.handle(@deferred) を実行します。handle は self の @state が :resolved なので、handler の onresolved コールバックに self の @value である 42 を引数にしてコールバックを呼び出します。すると戻り値 88 が返ってきます。

(Proc{|x| p x; 88 }).call(42) #=> 88

戻り値を実引数にして、handler の promise に resolve メッセージを送ります。この Promise は prom1 のことです。

handler.proimse.resolve(88)

これで prom1 の @state が :resolved に、@value が 88 に変わります。この Promise の @deferred は nil なので、これ以上やるべきことはありません。

prom0=#<Promise @state=:resolved @value=42
        @deferred=#<Handler onresolved=Proc{|x| p x; 88 } onrejected=nil
          promise=prom1>>
prom1=#<Promise @state=:resolved @value=88 @deferred=nil>

_then コールバックが Promise を返すとき

prom0 = Promise.new
prom1 = nil
prom2 = prom0._then{|x| prom1 = Promise.new{|promise| promise.resolve(x + 46) } }
prom3 = prom2._then{|x| p x; 96 }   # (1)
prom0.resolve(42)                   # (2)

(1) の直後。ペンディング状態の 3 つの Promise がつながっています。prom1 はまだ作られていません。

prom0=#<Promise @state=:pending, @value=nil,
        @deferred=#<Handler onresolved={|x| prom1 = .. } }, onrejected=nil,
          promise=prom2>>
prom1=nil
prom2=#<Promise @state=:pending, @value=nil,
        @deferred=#<Handler onresolved={|x| p x; 96 }, onrejected=nil,
          promise=prom3>>
prom3=#<Promise @state=:pending, @value=nil, @deferred=nil>

(2) の直後。すべての Promise が :resolved 状態に変化しています。prom1 も生成されており、prom1 の値が prom2 の値に渡されています。prom1 は、一時的な Promise なので、_then チェインにはつながっていません。

prom0=#<Promise @state=:resolved, @value=42,
        @deferred=#<Handler onresolved={|x| prom1 = .. }, onrejected=nil,
          promise=prom2>>
prom1=#<Promise @state=:resolved, @value=88, @deferred=nil>
prom2=#<Promise @state=:resolved, @value=88,
        @deferred=#<Handler onresolved={|x| p x; 96 }, onrejected=nil,
          promise=prom3>>
prom3=#<Promise @state=:resolved, @value=96, @deferred=nil>