domenic / promises-unwrapping

The ES6 promises spec, as per September 2013 TC39 meeting
1.23k stars 94 forks source link

CallHandle improperly transforms a derived promise. #52

Closed 6XGate closed 10 years ago

6XGate commented 10 years ago

It seems that when a Promise is fulfilled or rejected, the CallHandle will call the proper fulfill or reject handler on the derived Promise, but it doesn't seem to be aware of which handler it is calling and will always set the derived Promise state to fulfilled, even though the origin Promise is rejected.

Example code:

var p = [];
p.push(new Promise(function(resolve, reject) { reject() }))
p.push(p[0].then(function() { console.log('fulfilled') }, function() { console.log('rejected') }))
p.push(p[1].then(function() { console.log('fulfilled') }, function() { console.log('rejected') }))

This will produce the following output:

rejected
fulfilled

And the states of each Promise are as follows:

p[0] = {
    [[Reason]]: undefined,
    [[Value]]: Unset
}
p[1] = {
    [[Reason]]: Unset,
    [[Value]]: undefined
}
p[2] = {
    [[Reason]]: Unset,
    [[Value]]: undefined
}
6XGate commented 10 years ago

I would recommend creating a separate CallRejectHandler and CallResolveHandler.

CallResolveHandler(derivedPromise, handler, value)

  1. Queue a microtask to do the following:
    1. Let result be handler.[[Call]](undefined, (value))
    2. If result is an abrupt completion, call Reject(derivedPromise, result.[[value]])
    3. Otherwise, call Resolve(derivedPromise, result.[[value]]).

CallRejectHandler(derivedPromise, handler, reason)

  1. Queue a microtask to do the following:
    1. Let result be handler.[[Call]](undefined, (reason))
    2. If result is an abrupt completion, call Reject(derivedPromise, result.[[value]])
    3. Otherwise, call Reject(derivedPromise, reason).
Nathan-Wall commented 10 years ago

This is expected. You caught the rejection and handled it, so p[1] and p[2] shouldn't be in the rejected state. If you want to propagate the rejection, either:

var p = [];
p.push(new Promise(function(resolve, reject) { reject() }))
p.push(p[0].then(function() { console.log('fulfilled') }, function(r) { console.log('rejected'); throw r; }))
p.push(p[1].then(function() { console.log('fulfilled') }, function(r) { console.log('rejected'); throw r; }))

or

var p = [];
p.push(new Promise(function(resolve, reject) { reject() }))
p.push(p[0].then(function() { console.log('fulfilled') }))
p.push(p[1].then(function() { console.log('fulfilled') }))

In both of these cases p[0], p[1], and p[2] are all in the rejected state. In your example, p[1] goes into the fulfilled state because the code handles the rejection from p[0] and then implicitly returns undefined.

domenic commented 10 years ago

Indeed. Proof-reading appreciated though; thanks for looking into it!