Duck-type Promises

651 views
Skip to first unread message

Kris Kowal

unread,
Dec 3, 2010, 1:18:16 PM12/3/10
to CommonJS
In our last conversation on promises, Dean Landolt reminded us that
the strongest case for non-opaque promise objects, particularly
defining a duck-type for promises, was that it would permit multiple
instances of the promise library in the same program to exchange
promise objects.

TL;DR: I'm willing to entertain a new specification that defines
promises as a duck-type as in Promises/A but also provides a basis for
the Q API laid out in Promises/B. Promises would be defined as an
object that has a function with a particular name that can receive
arbitrary messages of the form promise[DUCK](operator, resolved, …)
and would be required to return rejected promises for unsupported
operators. I've coded this up to verify that the approach would work
and would in fact be a trivial change.

...

Thenables serve as a duck-type: any object with a "then" method is
considered a promise, but it lacks the generality of the "ref_send"
API because it only permits one message type ("then") to be sent
instead of a gamut of message types ("when", "get", "put", "post", and
arbitrary extensions with method-missing fallback).

Tyler Close's implementation of "ref_send" uses function objects as
the duck type. You send messages to a promise simply by calling it:

promise("when", resolved, rejected)
promise("get", resolved, "a")

So, I've revised my implementation of Promises/B in my
"experiment-ducks" branch of the Q package for NPM to use a
"duck-type". Here's the change-set.

https://github.com/kriskowal/q/commit/424e0f8c8d5c8974bcaf71d18bf2400abca33f22

There are no behavioral regressions. The "emit" method serves to
denote the duck-type, but I've changed the name of the method to an
unwieldy string as a place-holder. Since this string is never directly
used, and since it is ideal to avoid collisions, we are free here to
specify an unwieldy name.

This change implies the following specification:

A promise is an object that has a property named $DUCK$ that is a function.

And then the specification would need to define exactly how the Q API
sends messages to the promise object. This is essentially codified by
the implementation and restricted by the Promises/B specification.
For example, this is how the "Q.when" method works:

https://github.com/kriskowal/q/blob/424e0f8c8d5c8974bcaf71d18bf2400abca33f22/lib/q.js#L303-320

As you can see, this guards against erroneous and malicious
implementations of the DUCK method, preserving the guarantees charted
in Promises/B, while also observing the constraint that the promise
object is a duck-type.

...

I created an item that can be commented and voted on in our
bettermeans.com workstream.
https://secure.bettermeans.com/projects/520

Kris Kowal

Neville Burnell

unread,
Dec 4, 2010, 1:51:29 AM12/4/10
to CommonJS
* BIKESHED *

I'm thinking that if we use this type of duck typing for future
alternative specs, then the choice of $DUCK$ is no descriptive so
perhaps $PROMISE$ would be better.

Sorry for the noise!
>    https://github.com/kriskowal/q/commit/424e0f8c8d5c8974bcaf71d18bf2400...
>
> There are no behavioral regressions.  The "emit" method serves to
> denote the duck-type, but I've changed the name of the method to an
> unwieldy string as a place-holder. Since this string is never directly
> used, and since it is ideal to avoid collisions, we are free here to
> specify an unwieldy name.
>
> This change implies the following specification:
>
>     A promise is an object that has a property named $DUCK$ that is a function.
>
> And then the specification would need to define exactly how the Q API
> sends messages to the promise object.  This is essentially codified by
> the implementation and restricted by the Promises/B specification.
> For example, this is how the "Q.when" method works:
>
>    https://github.com/kriskowal/q/blob/424e0f8c8d5c8974bcaf71d18bf2400ab...

Kris Kowal

unread,
Dec 4, 2010, 2:12:06 AM12/4/10
to comm...@googlegroups.com
On Fri, Dec 3, 2010 at 10:51 PM, Neville Burnell
<neville...@gmail.com> wrote:
> * BIKESHED *

$DUCK$ was intended as a place-holder for any other, reasonably
unlikely to collide name. It was essentially "emit" in my former
implementation, but that is not sufficient to distinguish it from many
other objects that are legitimate resolution values. So, yeah, when it
comes time to bikeshed, we'll need a list of nominees.

Kris

Kris Kowal

unread,
Jan 5, 2011, 10:01:18 AM1/5/11
to comm...@googlegroups.com
I've drafted up a proposal for "promiseSendables" promise implementations.

http://wiki.commonjs.org/wiki/Promises/D

These combine the strength of "thenables" that there can be multiple
interoperable instances of the promise API defined in Promises/B, and
the ability to forward arbitrary messages to promises (not just
"then/when"). This capability is important for using promises as
primitives in distributed computing, which I've managed to get
prototyped in https://github.com/kriskowal/q-comm using a Promises/B
implementation.

Kris Kowal

Irakli Gozalishvili

unread,
Jan 5, 2011, 10:29:22 AM1/5/11
to comm...@googlegroups.com
Hi Kris,

1. Even though functions in js are instances of Object I think it's still better to make it clear that function can be a promise, by rephrasing:
A promise is an object / function that implements the "promiseSend" method.

2. A promise MUST implement "valueOf" ? Should not spec clarify what does return value of that method is ?    

Regards
--
Irakli Gozalishvili
Web: http://www.jeditoolkit.com/
Address: 29 Rue Saint-Georges, 75009 Paris, France



--
You received this message because you are subscribed to the Google Groups "CommonJS" group.
To post to this group, send email to comm...@googlegroups.com.
To unsubscribe from this group, send email to commonjs+u...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/commonjs?hl=en.


Kris Kowal

unread,
Jan 5, 2011, 10:31:18 AM1/5/11
to comm...@googlegroups.com
On Wed, Jan 5, 2011 at 7:29 AM, Irakli Gozalishvili <rfo...@gmail.com> wrote:
> Hi Kris,
>
> 1. Even though functions in js are instances of Object I think it's still
> better to make it clear that function can be a promise, by rephrasing:
> A promise is an object / function that implements the "promiseSend" method.

Sure.

> 2. A promise MUST implement "valueOf" ? Should not spec clarify what does
> return value of that method is ?

The second section describes the proper return-values for "valueOf()"
in each of the promise roles. I'll annotate.

Kris Kowal

Kris Kowal

unread,
Jan 5, 2011, 10:33:25 AM1/5/11
to comm...@googlegroups.com
On Wed, Jan 5, 2011 at 7:31 AM, Kris Kowal <kris....@cixar.com> wrote:
> The second section describes the proper return-values for "valueOf()"
> in each of the promise roles. I'll annotate.

On second thought, all objects respond to valueOf() anyway, so it's
non-normative and I've removed this point. The clarifications on what
valueOf() should return remain in the second section.

Kris Kowal

Kris Kowal

unread,
Jan 5, 2011, 10:41:52 AM1/5/11
to comm...@googlegroups.com
Also, Kevin Smith (khs4473) pointed out on Github that valueOf() might
need further clarification and possibly a special duck-type for
rejected promises to assist in interoperably distinguishing rejected
promises.

Kris Kowal

khs4473

unread,
Jan 5, 2011, 2:41:34 PM1/5/11
to CommonJS
After thinkin on it, a rejected promise is a promise that by
definition executes the argument to the "when" message, right? We
could use that to test whether it's a rejection or not. Not really
performant, but...

Kris Kowal

unread,
Jan 5, 2011, 2:46:33 PM1/5/11
to comm...@googlegroups.com
On Wed, Jan 5, 2011 at 11:41 AM, khs4473 <khs...@gmail.com> wrote:
> After thinkin on it, a rejected promise is a promise that by
> definition executes the argument to the "when" message, right?  We
> could use that to test whether it's a rejection or not.  Not really
> performant, but...

Not really. The trick with Q.isRejected(promise) is that it has to
return a literal value in the same turn as the call. Q.when must
guarantee that the callbacks are called in future turns. But, come to
think of it, we could use Q.asap in my prototype, although there are
folks here who would like Q.asap to die a quick death, probably
rightly so.

Kris Kowal

khs4473

unread,
Jan 5, 2011, 7:12:17 PM1/5/11
to CommonJS
Seems like we'd want to be able to duck-type the rejection then, like
obj.promiseIsRejection or something.

Also, I finally pretty much get it all, except for the def()
function. How is that used?


On Jan 5, 2:46 pm, Kris Kowal <kris.ko...@cixar.com> wrote:

Kris Kowal

unread,
Jan 5, 2011, 7:20:38 PM1/5/11
to comm...@googlegroups.com
On Wed, Jan 5, 2011 at 4:12 PM, khs4473 <khs...@gmail.com> wrote:
> Seems like we'd want to be able to duck-type the rejection then, like
> obj.promiseIsRejection or something.
>
> Also, I finally pretty much get it all, except for the def()
> function.  How is that used?

Q.def(object) locks an object to the current worker. This is necessary
to distinguish JSON serializable objects from instances with methods
that close on internal, unserializable state, when you have promises
communicating through message passing.

This is an example of the use of Q.def that allows a server to give a
client access to a local object with a shutdown method:

https://github.com/kriskowal/q-comm/blob/master/examples/messages.2/server.js#L7-11

A client gets a promise for this remote object here:

https://github.com/kriskowal/q-comm/blob/master/examples/messages.2/shutdown.js#L12

And thus can send a "shutdown" message to the remote object. Without
Q.def around the object on the server-side, the client would have
resolve the "remote" promise with the JSON
serialization-deserialization of the remote object, which would have
been simply an empty object {} since closures are not serialized by
JSON.

Kris Kowal

khs4473

unread,
Jan 5, 2011, 9:23:22 PM1/5/11
to CommonJS
OK - it'll take a few readings to get. Where does "def" come from?

On Jan 5, 7:20 pm, Kris Kowal <kris.ko...@cixar.com> wrote:
> On Wed, Jan 5, 2011 at 4:12 PM, khs4473 <khs4...@gmail.com> wrote:
> > Seems like we'd want to be able to duck-type the rejection then, like
> > obj.promiseIsRejection or something.
>
> > Also, I finally pretty much get it all, except for the def()
> > function.  How is that used?
>
> Q.def(object) locks an object to the current worker. This is necessary
> to distinguish JSON serializable objects from instances with methods
> that close on internal, unserializable state, when you have promises
> communicating through message passing.
>
> This is an example of the use of Q.def that allows a server to give a
> client access to a local object with a shutdown method:
>
> https://github.com/kriskowal/q-comm/blob/master/examples/messages.2/s...
>
> A client gets a promise for this remote object here:
>
> https://github.com/kriskowal/q-comm/blob/master/examples/messages.2/s...
Reply all
Reply to author
Forward
0 new messages