promises-aplus / constructor-spec

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

Assimilation as a construction strategy?? #15

Open domenic opened 11 years ago

domenic commented 11 years ago

This came out of a discussion with MarkM and @kriskowal. It seems promising.

Start from this formulation of the "promise constructor" idea we've seen in many other issues so far:

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

Imagine if instead we specced a thenable assimilator, we'll call it when. Then we could instead accomplish the above with:

var promise = when({ then: function (resolve, reject) {
  resolve(5);
} });

This is between one and three more characters, depending on if you prefer {x:y} or { x: y }.

ES6 style:

let promise = new Promise((resolve, reject) => resolve(5));
let promise = when({ then(resolve, reject) => resolve(5) });

This time between -1 and +1 character deltas.

Q of course gets it easy, because our assimilator would be called Q, making it a win on characters in every case.


Important note: then must be executed in the next turn of the event loop to prevent malicious thenables from breaking execution flow. (On the plus side, this means that any calls to resolve or reject can synchronously inform the returned promise, since we're already in the next tick.)


Alternately we could spec both assimilation and construction at the same time, given an equivalence between two. Maybe new Promise((resolve, reject) => { ... }) is equivalent to Promise.from({ then(resolve, reject) => { ... })?

ForbesLindesay commented 11 years ago

I like it. Things to consider:

  1. How does extending to support progress look
  2. How does extending to support cancellation propagation look
  3. Behavior when then throws an error
  4. Is resolve polymorphic

My thoughts:

1: Simple, lots of implementations already provide an onProgress argument to then.

2: Could be as simple as:

let promise = when({ then(resolve, reject) => resolve(5),
                     cancel() => xhr.abort() });

3: I think it should be equivalent to calling reject

4: not sure yet

ForbesLindesay commented 11 years ago

The more small failings I see with other ideas the more I like this one. Everything that's really hard to get right for any other spec is really easy with this one.

I still think it should be a constructor, so that:

promise instanceof Promise

is always true.

let promise = new When((resolve, reject) => resolve(5));
let promise = new When({ then(resolve, reject) => resolve(5) });
briancavalier commented 11 years ago

This is definitely interesting, and I'm personally a fan of the functional style.

I believe that when.js and Q are both effectively compliant with this proposal.

Given that each library will have their own Promise constructor, what are the near-term advantages of promise instanceof Promise over the standard duck type test? Or is the primary benefit looking (waaay) ahead to the day when there is some official Promise constructor?

domenic commented 11 years ago

The advantage of the promise constructor is as in #5: it allows utility libraries to create promises that match those of the implementation in use. I.e., in this case:

function timeout(promise, time) {
  var Promise = promise.constructor;

  return new Promise({ then: function (resolve, reject) {
    promise.then(resolve);
    setTimeout(function () {
      reject(new Error('Operation timed out.'));
    }, time);
  } });
}
domenic commented 11 years ago

I think it's a bit awkward to have the constructor use the assimilation signature, though, thus what I proposed in #18.

ForbesLindesay commented 11 years ago

I really only want the constructor stuff for discoverability, and perhaps we should do that another way and in another spec. We'd still have real problems deciding whether the constructor we'd found was a Resolvers/A+ constructor anyway.

Provided people agree with me that: when then throws an error we turn that into a rejection I'm fully in favor of going forward with just specifying assimilation as our only construction method (assuming we can get buy in from the library authors).

novemberborn commented 11 years ago

Even though I understand how this would work, it seems too weird a concept creating promises through assimilation. "I have to call when(), and pass it an object that has a then(), but I'm not calling then(), it gets called for me… argh just let me have defer()!"