Proposal: Promises/KISS

152 views
Skip to first unread message

AJ ONeal

unread,
Oct 26, 2010, 1:24:32 AM10/26/10
to comm...@googlegroups.com, futures-j...@googlegroups.com
After having created the library FuturesJS and after using it for a while in the Browser and with Node.js I'd like to submit Promises/KISS to CommonJS for consideration.

This is the roughly direction which I intend to take Futures 2.0.



Example Usage:
(view with syntax http://gist.github.com/646206)

var Promise = require('futures');

function getUnavailableData(params) {
  var p = Promise.create({ updatable: true, timeout: Infinity, freeze: false });
  // p.fulfill(err, data, data, ...);
  setInterval(askUserHowHeFeelsAboutLife, 60*60*1000, p.fulfill);
  return p.protected(); // returns a different object which has no fulfill method
}

function useUnavailabledata() {
  var p = getUnavailableData();
  if (! (p instanceof Promise)) {
     throw new Error("How odd, not a promise instance");
  }
  if ('function' !== typeof p.when) {
     throw new Error("How odd, not a promise by duck-typing");
  }
  p.when(function (err, data, data, ...) {
    // store data in cache
  });
  if (p.options.updatable) {
    p.whenever(function (err, data, data, ...) {
      // update data in cache
    });
  }
  p.ready // true once resolved
}

var j = Promise.join(options);
j.add(p1, p2, p3);
or
j.add([p1, p2, p3]);





Argument:


The point of a promise is to immediately return data which is not immediately available. The essence of a promise is a callback function.

The benefits of a promise which distinguishes it from a normal callback
  • the user can determine that it is a promise
  • a particular set of data is associated with it
  • multiple interested parties can use that data
  • it can be easily joined with other promises

The specification should be simple enough to be
  • easily understood
  • implemented in the Browser
  • implemented in a few days
  • used as a drop-in replacement in for current callbacks methods of doing the same thing.
  • progressively enhanced
  • extended by vendors without severe compatibility issues
  • good enough
In the spirit of "Getting Real", why not put a deadline on it: Proposal will be chosen by xDay of xMonth and pick what works well and cut features and specs so that something is available by the deadline. Then work on a 2.0 with another deadline.






Specification / API:
  • vendor may name the module
  • Ex: var Promise = require('myPromiseLib');

  • vendor may choose how to create the promise
  • Ex: Promise.create(options) or new Promise(options)

  • Promise must be 'self-documenting' (enumerable methods, rather than being lost in the global namespace)

  • Promise must be duck-type detectable.
  • Ex: if (p.when) {//is promise}
  • Ex: if (p.whenever) {//is updatable promise}
  • Ex: if (p.options.cow) {// has supercow powers from vendor x}

  • Promise should be instanceof detectable (see example in gist above)

  • The instantiation of the promise must allow for optional parameters, at least:
    • timeout. If the promise isn't resolved in this number of ms the 'timeout' error is issued with whatever data is available
      the default value should be set to 10 seconds
      The user should always set the timeout value. It is 10 seconds by default
    • updatable. If the promise will accept multiple resolutions.
    • freeze. By default all promised items will be frozen or copied, but this may be disabled

  • `when` and `whenever` must allow multiple callbacks to be registered
    • the second argument to `when` is reserved for a params hash
    • 'Err', which may be any type of literal, array, object or exception must be the first argument passed to the function registered with `when` or `whenever`

      This promotes good coding: The less work it is for someone to handle an error, the more likely they are to handle it. From my own experience I've found that I handle errors much more often in Node than I did in jQuery because the convention is to hand it back as the first param

  • `join` should join multiple promises in the order in which they are passed in.
  • If updatable
    • will resolve every time that each promise has resolved at least once
    • will throw if the promises added are not updatable
  • Ex: var j = Promise.join({updatable: true});
    j.add(p1, p2, p3); // also accepts array j.add([p1, p2, p3])
    j.add(p4); // throws exception
    j.when(function (args1, args2, args3) {});

Many things - like get, post, put, del can be implemented atop the promise.
Something such as an update handler could be included in a future version.

The lowfat version of this spec would be to remove the "updatable"/"whenever", but definitely leave the join and leave the options for vendor extensions.

To me it seems that a promises spec without a join spec isn't worth the effort - it's just a formalization of a callback.


AJ ONeal

AJ ONeal

unread,
Nov 4, 2010, 12:31:47 AM11/4/10
to comm...@googlegroups.com, futures-j...@googlegroups.com
bump.

Any feedback?

AJ ONeal

Kris Kowal

unread,
Nov 4, 2010, 1:07:48 AM11/4/10
to comm...@googlegroups.com
On Wed, Nov 3, 2010 at 9:31 PM, AJ ONeal <cool...@gmail.com> wrote:
> bump.
> Any feedback?

Yeah, most of your requirements are ones that I agree with and believe
that Promises/A and Promises/B both satisfy and have satisfied. If I
don't mention in this email one of the requirements you feel are not
satisfied, please follow-up and I'll try to explain. The upshot is
that I think we're largely in agreement philosophically. Here are the
exceptions:

Regarding "joins", "when" is the only primitive we need. A *wide*
variety of conjunctions can be sensibly and readably constructed in
terms of that one function. Most other forms are solved better by
closures and Array's "reduce" function than more methods.

https://github.com/kriskowal/q-util/blob/master/lib/q-util.js

I disagree that promises are merely a codification of a callback.
They model both of the behaviors of function control-flow projected
into the asynchronous control-flow world: return values *AND*
exceptions, particularly that an ignored exception percolates
implicitly. There are numerous other reasons that both Promises/A and
Promises/B are far more than just callback styles, and certainly
useful in the absence of joining utilities which can be trivially
implemented by libraries in terms of the promises primitives.

It is trivial to implement a timeout without any additional effort in the spec:

setTimeout(deferred.reject, 1000);

Multiple resolutions are a composition hazard. You would end up with
confused clients. If you need multiple calls, you need to use an
event emitter, not a promise, or a function call that returns a
promise for the most recently, or first eventually emitted value. In
any case, no work is needed on our part.

As for the deferred methods bike-shed, I'm quite content with the bits
that Kris Zyp, Tyler Close, and I appear to all be in agreement on so
far: "resolve", "reject", and all that.

Regarding protection, I continue to be in agreement with Mark Miller
and Tyler Close that cleanly separating the "promise" and "resolve"
components of a "defer" result solves the problem, and is not
syntactically expensive enough in practice to fuss about. I almost
always buy peace-of-mind when it's on sale. I don't think unfrozen
promises are likely to be useful in environment where frozen promises
are available.

"get", "put", "post", and "del" are important and Tyler Close's
implementation illuminates that providing hooks for these "extensions"
to the specification is at least important, but I'm not about to press
for a vote on Promises/A vs Promises/B. We Kris's have cast our bets
with our respective proposals and are building out essentially
competing ecosystems. Nathan Stott is presently trying to figure out
exactly how difficult it is to exchange code between these systems,
which is probably the most useful thing anybody could be doing about
promises right now since it reveals details inherent to our
assumptions. Meanwhile, I will be working with Mark Miller, Terry
Stanley, Tyler Close, and Dirk Neumann to port utilities for the
already vetted remote promise object methods, get/put/post/del to
demonstrate their worth in some of our mutual projects. In the worst
case, they are easy enough to ignore.

Kris Kowal

Reply all
Reply to author
Forward
0 new messages