promises-aplus / constructor-spec

Discussion and drafts of a possible spec for creating and resolving promises
10 stars 4 forks source link

Strawman: Promise Creation API/C #17

Closed domenic closed 11 years ago

domenic commented 11 years ago

Terminology

  1. "Settled" means either fulfilled or rejected.

    The Promise Constructor

An implementation of this specification supplies a function responsible for constructing new promises. This promise constructor takes as its sole argument a user-supplied factory function which is given the means to settle the constructed promise.

Here we refer to this promise constructor by the name Promise for concreteness, although the name is implementation-specific.

var promise = new Promise(factory);
  1. Can be called without new, with the same results.
  2. promise instanceof Promise must be true.
  3. Object.getPrototypeOf(promise) === Promise must be true.
  4. promise.constructor === Promise.prototype.constructor === Promise must be true.
  5. Promise.length === 1 must be true.
  6. If factory is not a function, the implementation must throw a TypeError.
  7. If factory is a function,
    1. It must be called with the factory parameters.
    2. It may only be called when the function execution stack is empty. (note 1)

      The Factory Parameters

When factory is called, it is given two parameters, which we call here resolve and reject. In this way, factory obtains the means to settle a promise.

var promise = new Promise(function (resolve, reject) {
  // ..
});

Calling resolve(x)

  1. If x is a non-thenable,
    1. If promise is pending, promise must be fulfilled with x as its fulfillment value.
    2. If promise is settled, nothing happens (in particular, no exception may be thrown).
  2. If x is a thenable, promise must attempt to adopt the state of x (see Promises/A+ spec).

    Calling reject(reason)

  3. If promise is pending, promise must be rejected with reason as its rejection reason.
  4. If promise is settled, nothing happens (in particular, no exception may be thrown).

    Thenable Assimilation (Optional)

Optionally, an implementation may provide the capability to "assimilate" thenables, turning them into real promises. If so, this is done with a property from of the promise constructor:

var promise = Promise.from(thenable);
  1. If thenable is not an object or function, throw a TypeError.
  2. If thenable does not have a property then that is a function, throw a TypeError.
  3. Otherwise, return the result of new Promise(thenable.then).

    Notes

  4. In practical terms, an implementation must use a mechanism such as setTimeout, setImmediate, or process.nextTick to ensure that factory is not called in the same turn of the event loop as the call to the promise constructor. Note that if factory throws an exception, it will necessarily be uncatchable and not interfere with any surrounding code, since the execution stack is empty.
domenic commented 11 years ago

The only thing I don't like about this is that it makes it hard for extensions like notifyAboutProgress and cancel, i.e. it has the problem summed up in #7. I guess it could be specced to pass factory an object (what's a good name for such an object?) that has resolve and reject methods.

domenic commented 11 years ago

Also, I'm leaving thrown errors uncatchable, but could be convinced that they should become rejections. But then we have the whole "what if you do resolve(5); throw 10;?" issue.

domenic commented 11 years ago

It turns out I had massive confusion over the behavior we want from calling resolve (or reject) on a promise that is waiting on a pending promise. In Q:

var d = Q.defer();
d.resolve(Q.delay("a", 1000));
d.resolve("b");

// after 1000 ms, `d.promise` is fulfilled with "a".

As specced here, a d.promise is not settled when d.resolve("b") is called, so as specced here, d.promise would immediately be fulfilled with "b".

Thus, replacing this with #18.