#sync breaks when mixing Promise classes
haileys opened this issue · comments
I'm running into a bug in promise.rb where the sync
method breaks when mixing various Promise classes together.
Here's some sample code demonstrating the problem:
require "promise"
class MyPromise < ::Promise
def wait
fulfill(456)
end
end
p1 = Promise.resolve(123)
p2a = MyPromise.new
p2a.sync
p2b = MyPromise.new
p3a = p1.then { |a| p2a.then { |b| a + b } }
p3b = p1.then { |a| p2b.then { |b| a + b } }
puts p3a.sync
puts p3b.sync
What's happening is that when then
is called on p1
(which is already resolved), promise.rb internally creates a new promise of the same type as p1
and then immediately executes the then
block. Even though the then
block is called immediately and returns a new MyPromise
instance, the internal state of the promise returned by the then block is copied into the newly created Promise
instance rather than the MyPromise
instance being returned directly.
If p2a
is also already resolved, this is not a problem and calling p3a.sync
returns the right thing.
If p2b
is not already resolved when p3b.sync
is called, the sync
call tries to call wait
on an instance of Promise
, rather than an instance of MyPromise
. This raises a NoMethodError.
The output I'd expect the code sample above to produce is:
579
579
The actual output is:
579
lib/promise.rb:64:in `sync': undefined local variable or method `wait' for #<Promise:0x007fb8db51e028 @state=:pending, @callbacks=[]> (NameError)
from x.rb:20:in `<main>'
Yes, that is the problem with mixing promise classes. This can be avoided by using p1 = MyPromise.resolve(123)
so that you don't mix promise classes.
However, I don't think this is a bug.
To start with, you have an incorrect assumption that the block passed to then is executed immediately when the receiver Promise instance has already been resolved. The promise spec expects the onFulfilled or onRejected to be deferred. Promise#defer can be overridden to implement this behaviour and conform to the spec as documented in the README's usage section. This prevents the Promise#then method from being able to determine the class of a promise that may be returned from the block.
The other problem with what you are suggesting is that it would introduce racey behaviour in the type of value that is returned from Promise#then in code where the Promise instance could be resolved by another fiber or thread that is running in parallel. I would much prefer to have Promise#then deterministically return a result based on the type of the receiver so that it is easier to catch errors from misuse during testing.