promises-aplus / constructor-spec

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

discovery of resolver #5

Open ForbesLindesay opened 11 years ago

ForbesLindesay commented 11 years ago

Consider a utility function, timeout:

function timeout(prom, time) {
  let {promise, resolver} = promise();
  resolver.resolve(prom);
  setTimeout(function () {
    resolver.reject(new Error('Operation timed out');
  }, time);
  return promise;
}

The problem with utility functions like these is that I'm not going to get out the same type of promise that I put in. If I passed in something like a Q promise and get back something like a promises-a I loose a lot of functionality.

What if promises came with a property that tells you how to make a new promise, resolver pair:

function timeout(prom, time) {
  let {promise, resolver} = typeof prom.defer === 'function' ?
                            prom.defer() : promise();
  resolver.resolve(prom);
  setTimeout(function () {
    resolver.reject(new Error('Operation timed out');
  }, time);
  return promise;
}

That way if I passed a Q promise in, I'd get a Q promise out.

Do people want this method? What should it be called?

domenic commented 11 years ago

This is a pretty interesting idea. A bit weird, but not bad.

If we went with a promise-constructor approach, though, it would be more natural, via the constructor property:

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

  return new Promise(function (resolve, reject) {
    promise.then(resolve, reject);
    setTimeout(function () {
      reject(new Error('Operation timed out.'));
    }, time);
  });
}

Writing this down actually gives me the strongest argument for the constructor pattern over the deferred pattern.

unscriptable commented 11 years ago

This seems like a great way to achieve all of the desired features:

    new Promise(function (resolve, reject) {
      resolve(promise);
      setTimeout(function () {
          reject(new Error('Operation timed out.'));
          // reject function has its own "extension" properties
          // so you could also do this (feel free to bikeshed these names):
          // resolve.createRejectedPromise(new Error('Operation timed out.'));
          // or this:
          // resolve.createCancellablePromise(promise);
          // or this:
          // resolve.createSomeObservablePromise(promise);
      }, 100);
    });

Pros:

  1. fulfillment is obvious and straightforward via function signature. 99% of the time, this is what devs will need
  2. first param can be treated as a resolver with several functions, even proprietary extensions
  3. KISS

Cons:

  1. duplicating the reject function arg as a resolve arg property might feel silly to some
briancavalier commented 11 years ago

I like this hybrid. It makes the most common operations, resolve & reject, totally obvious and easy to use, while still allowing the resolve function (or reject, I suppose) to be used as the extension point for other to-be-spec'd and proprietary features.

ForbesLindesay commented 11 years ago

@unscriptable @briancavalier both those comments are nothing to do with this issue and everything to do with #7. This issue is about discovering what promise library was used to create an existing promise (so as to be able to create a new promise from the same library). You're comments are about what arguments should be passed to the resolver function.

juandopazo commented 11 years ago

I worry about the memory footprint of having resolve as both a function and an object with the resolver methods. If we go that way for each promise we'll have to create:

And we don't have __proto__ to avoid creating all those functions. I thought about setting this in the Promise initialization function pointing to the resolver, but it's a little weird.

ForbesLindesay commented 11 years ago

@juandopazo I don't see how your comments relate to this issue, I assume you are still talking about #7.

juandopazo commented 11 years ago

Yup, my bad.