Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Strawman: Promise Creation API/B #13

Closed
domenic opened this issue Jan 27, 2013 · 7 comments
Closed

Strawman: Promise Creation API/B #13

domenic opened this issue Jan 27, 2013 · 7 comments

Comments

@domenic
Copy link
Member

domenic commented Jan 27, 2013

Terminology

  1. "Settled" means either fulfilled or rejected.

The Promise Constructor

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.prototype must be true.
  4. promise.constructor === Promise.prototype.constructor === Promise must be true.
  5. If factory is not a function, the implementation must throw a TypeError.
  6. If factory is a function, it must be called immediately (i.e. within the same turn of the event loop), with a resolver. (note 1)

The Resolver

var promise = new Promise(function (resolver) {
  // ..
});

The resolver is a function with several properties, which are also functions.

  1. Resolver functions must not be dependent on their this value.

resolver(x)

  1. If x is a non-promise,
    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 promise, promise must assume the state of x (see Promises/A+ spec).

resolver.fulfill(value)

  1. If value is a non-promise,
    1. If promise is pending, promise must be fulfilled with value as its fulfillment value.
    2. If promise is settled, nothing happens (in particular, no exception may be thrown).
  2. If value is a promise, the implementation must throw a TypeError.

resolver.reject(reason)

  1. If reason is a non-promise,
    1. If promise is pending, promise must be rejected with reason as its rejection reason.
    2. If promise is settled, nothing happens (in particular, no exception may be thrown).
  2. If reason is a non-promise, the implementation must throw a TypeError.

resolver.yield(otherPromise) [normative optional? cut entirely?]

  1. If otherPromise is a promise,
    1. If promise is pending, promise must assume the state of otherPromise (see Promises/A+ spec).
    2. If promise is settled, nothing happens (in particular, no exception may be thrown).
  2. If otherPromise is not a promise, the implementation must throw a TypeError.

Notes

  1. Note that if factory throws an exception, since it is called within the same turn of the event loop as new Promise, promise will never be successfully created and the error will be uncaught by the implementation. [is this note even necessary?]
@domenic
Copy link
Member Author

domenic commented Jan 27, 2013

This strawman is based on a few guiding ideas:

  • We must prevent promises-for-promises from being created.
  • Polymorphic fulfillment/yielding behavior, as per return in the Promises/A+ spec, is useful: thus resolver's function behavior.
  • Polymorphic rejection behavior, as per throw in Update exception handling section to cover throwing a promise promises-spec#66, is not useful. It's specified to prevent promises-for-promises, but is not something people want available to use.
  • Explicit and clearly understandable code is valuable:
    • resolver.fulfill(value) fulfills the promise with value
    • resolver.reject(reason) rejects the promise with reason
    • resolver.yield(otherPromise) yields the promise to otherPromise
  • Simple is valuable: there is no reaction to factory returning a value or throwing an exception.
  • The "class" constructor pattern is valuable: it allows instanceof and constructor-discovery, and provides a strong idiomatic way of creating new objects---including promises.
  • Mutually suspicious resolvers, or "settlement races," should be accomodated: you can hand out a resolver function to a producer and he cannot know the state of the promise it's attached to, e.g. by checking if resolver.fulfill throws an AlreadySettledError.

@ForbesLindesay
Copy link
Member

I don't like Notes-1:

Consider this function:

function doAsyncWork() {
  return new Promise(function (resolver) {
    var resA = doSynchronousPreparationWhichMightThrowAnException();
    resolver.resolve(doAsyncWork(resA));
  });
}

If you want to call it and be sure of handling all errors you need code to handle both synchronous and asynchronous errors, this is really bad.

Otherwise this is mostly fine. I'd be inclined to cut yield entirely. Especially given it's potential confusion with the ES6 yield keyword.

@bergus
Copy link

bergus commented Feb 11, 2013

I strongly oppose points 3 and 4. This would prevent any inheritance approaches (and this constructor proposal is all about inheritance, isn't it?). While I really like instanceof, settling the [[prototype]] makes it unnecessary limited.

For example, in my https://github.com/bergus/F/blob/master/Promise.js I have implemented a Stream to be a "subclass" of Promise (basically with chunked fulfillments).

My proposal would be to omit point 3 (2 should be enough), and change 4 to:

  • promise.constructor must be a function with all the properties of a Promise constructor (1-6), yet it must not necessarily === Promise.

PS: Didn't we forget something substantial (point 0 or so):

  • promise must implement the Promise/A+ spec

@juandopazo
Copy link

I think points 3 and 4 only refer to a promise created from var promise = new Promise(fn).

@bergus
Copy link

bergus commented Feb 11, 2013

OK, that's good (though it excludes implementations that want to return subclass instances from the Promise constructor, maybe dependent on optional further arguments). However I fear it could confuse people (just as it confused me) and make them to (ab-)using these properties for type checking (especially the constructor thing).

Still they don't seem really necessary to me, it would be nice to add at least some reasoning. Also, point 3 should be fixed to

Object.getPrototypeOf(promise) === Promise.prototye // encloses Point 2, btw

@ForbesLindesay
Copy link
Member

Promise can be substituted for whatever library name you want to give it in this spec, so you would just need to ensure chunkifiedPromise.constructor === ChunkifiedPromise.prototype.constructor === ChunkifiedPromise etc.

@domenic
Copy link
Member Author

domenic commented Feb 12, 2013

Killing this in favor of #18, mainly because it contains terminology and behavior confusion around "resolved" and "settled". If someone wants something more like this, with argument-type checking, but with #18's not-confused resolution process, we can create a new draft.

@domenic domenic closed this as completed Feb 12, 2013
@bergus bergus mentioned this issue Apr 16, 2013
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants