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

Extended promise objects #6

Open
juandopazo opened this issue Dec 21, 2012 · 5 comments
Open

Extended promise objects #6

juandopazo opened this issue Dec 21, 2012 · 5 comments

Comments

@juandopazo
Copy link

Promise objects have only the then method and maybe a few other utility methods. But using promises for chaining asynchronous operations require more complex objects. For instance, jQuery's animate method should return a promise with an animate method so you can do $(selector).animate(opts1).animate(opts2).

I see two basic approaches to this: prototypical inheritance or "parasitic" inheritance (copying properties). How have you folks been dealing with this? What have you learned so far? Would you do anything differently?

@domenic
Copy link
Member

domenic commented Dec 21, 2012

I've been doing object-side prototypal inheritance in Chai as Promised. Basically:

function createExtendedPromise(promise) {
  var extendedPromise = Object.create(promise);
  extendedPromise.extraMethod = function () { };
  return extendedPromise;
}

This was somewhat necessary due to Q promises being frozen (and thus non-extensible).

@ForbesLindesay
Copy link
Member

I've sometimes used promises as a mixin:

function FluentAPI() {
  let {promise, resolver} = defer();
  this.then = function () {
    //start request
    //then delegate to real then
    return promise.then.apply(promise, arguments);
  };
  //done calls this.then anyway
  this.done = promise.done;
}

Which is necessary if you want a fluent API:

Request()
  .post(url)
  .with(data)
  .then(function (res) {
  });

@ForbesLindesay
Copy link
Member

I'm leaning towards saying we should go with Promise(fn) as a constructor, partly because it makes it really easy to do sane inheritance and extension of promise objects. With that in mind, we need to be very careful in the Promise constructor. This, for example, doesn't work:

function PromiseA(fn) {
  fn('RESOLVER_GOES_HERE');
  this.then = ...;
}

function PromiseB(fn) {
  PromiseA.call(this, fn);
}

PromiseB.prototype = new PromiseA();
PromiseB.prototype.constructor = PromiseB;
new PromiseB(function () {});

If we add a check for typeof fn === 'function' in the PromiseA constructor everything works great. We could then add all our extensions to B's prototype.

I think if we think it's desirable to have our promises frozen (as Q does) we should probably require people to instead do something like:

function PromiseA(fn) {
  fn('RESOLVER_GOES_HERE');
  this.then = ...;
}

function PromiseB(fn) {
  PromiseA.call(this, fn);
}

PromiseB.prototype = Object.create(PromiseA.prototype);
PromiseB.prototype.constructor = PromiseB;
new PromiseB(function () {});

Two key points to note about this:

  1. it breaks on ie older than 9. Personally I don't care and think we should do our best to never write websites that work on ie older than 9.
  2. Setting the constructor of PromiseB.prototype is essential in order for discovery of resolver #5 to be possible.

This also lets you extend the resolver object by wrapping fn in something which extends the resolver.

@lsmith
Copy link

lsmith commented Jan 14, 2013

@ForbesLindesay Breaks because of lack of Object.create()? There's the Object.create() polyfill for that. Though not fully feature compatible, it seems pragmatically appropriate here. I haven't done Y.prototype = new X(); in years :)

@ForbesLindesay
Copy link
Member

Yes, Object.create can be polyfilled sufficiently as:

function create(obj) {
  function F() {}
  F.prototype = obj;
  return new F();
}

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