kris@imoo:~/foo $ git clone git://github.com/kriskowal/narwhal.git
Initialized empty Git repository in /Users/kris/foo/narwhal/.git/
remote: Counting objects: 2884, done.
remote: Compressing objects: 100% (1291/1291), done.
remote: Total 2884 (delta 1802), reused 2463 (delta 1486)
Receiving objects: 100% (2884/2884), 2.28 MiB | 1094 KiB/s, done.
Resolving deltas: 100% (1802/1802), done.
kris@imoo:~/foo $ cd narwhal/
kris@imoo:~/foo/narwhal master $ bin/sea
PATH=/Users/kris/foo/narwhal/bin:/bin:/usr/bin
SEA=/Users/kris/foo/narwhal
SEALVL=1
kris@imoo:~/foo/narwhal master $ narwhal
Rhino 1.7 release 3 PRERELEASE 2009 04 05
js> var Q = require("promise");
You might find this example illustrative of the basic principles.
1
2 var Q = require("promise");
3
4 print('resolve immediately');
5 Q.when(10, function (a) {
6 print('first resolved '+ a);
7 });
8
9 var {promise:promise, resolve:resolve} = Q.defer();
10 Q.when(promise, function (a) {
11 print('second resolved ' + a);
12 });
13 print('enqueue resolution');
14 resolve(20);
15
16 print('end of first event');
17
This example illustrates multi-stage deference:
1
2 var Q = require('promise');
3
4 var {promise:a, resolve:aResolve} = Q.defer();
5 var {promise:b, resolve:bResolve} = Q.defer();
6
7 var foo = function (a, b) {
8 return Q.when(a, function (a) {
9 print('a resolved');
10 return Q.when(b, function (b) {
11 print('b resolved');
12 return a + b;
13 });
14 });
15 };
16
17 Q.when(foo(a, b), function (result) {
18 print(result);
19 });
20
21 print('resolve b');
22 bResolve(3);
23
24 print('resolve a');
25 aResolve(2);
The implementation appears to have a weakness in variadic arguments
(max 3), which I don't understand as yet and I'm still figuring out
the API.
Kris Kowal
I don't have strong opinions about how promises should work, but I
have inklings about what they're supposed to be like and how they can
be used in a generic event system and made simple and usable. So,
nothing depends on promise.js and you're welcome to fork/expierment
whatever. May the best fork win! :-)
Kris Kowal
I was kind of hoping they both would win. Like I had mentioned earlier,
they aren't mutually exclusive. There is a proposal for the API for the
promises themselves (which I referenced) and the API for creating and
routing promises (the "Q" API which you implemented). Is that not the
route you would like to see this go?
Kris
Oh, I see.
> Like I had mentioned earlier,
> they aren't mutually exclusive.
My apologies, I have not yet begun to discuss promises myself, being
not yet qualified, which is a problem for which I suspect many of us
have refrained from the discussion. But, given that I've come to
believe they're the right road to solving perennial problems, I'm
beginning to do my part to learn about them and in turn teach others,
as we must rapidly do given that unlike recursion, pointers, and
object-oriented programming, promises are not the bread and butter of
every fresh college grad.
> There is a proposal for the API for the
> promises themselves (which I referenced) and the API for creating and
> routing promises (the "Q" API which you implemented). Is that not the
> route you would like to see this go?
I think I would. I would like to see a more friendly face on the Q
API, and if that can be done with an abstraction layer, I'm all for
it. Given a quick glance, I actually prefer "when" over
"addCallbacks" though.
If you'll forgive a digression, I've been developing a "Codish"
lexicon on the side to help me keep names for ideas straight, and to
avoid mixing metaphors. On Planet Kris Kowal (Population: 1), the
words associated with events are:
- event
- observe
- signal
- send
- when (before, after)
- observable
- observer
- state
- promise
- binding
- break/reject/revoke
- reactor
"event" is an object that gets passed as the context to a chain of
observers. "event" usually implements the usual stuff for stopping
propagation and canceling the primary action.
"observe(name, observer)" gets a "signal" and adds an observer, with
"signal.when(observer)".
A "signal" is an object that can be observed when it is "sent" an
"event". In my present idealization of signals, they receive the
"event" as a context object and the arguments of the call to "send",
like "signal.send.apply(event, args)". A "signal" has an "action"
function that may or may not do anything, but is a fixed point around
which other signals get sent.
"when" is a function that attaches an observer to a signal and returns
a new "signal". I've also implemented in the past "before" and
"after" so that I can selectively attach events around particular
signals with very fine control. The context signal is the action of
the returned signal.
"signaler" is an object that implements "observe(name, observer)" and
"signal(name, event, args)".
An "observable" is a kind of signaler whose methods calls can be
observed by name. So, if I have an observable that has a "foo"
method, and I "observable.observe('foo', function (a, b, c) {})",
"observable.foo(1, 2, 3)" will cause an event to be sent to the "foo"
observer with itself as the context object and my arguments in place.
For an "observable", the original member function becomes the "action"
of the signal.
A "state" is a type of "signal" that begins unset, and then becomes
set when it is "sent" an "event", and remains set, such that any
"observers" that are registered after it is set are immediately sent
the "event". States are handy for capturing certain events on or
after their occurrence and sending the event to all observers exactly
once. In particular, I've had need of states for "onload" and
"DOMContentReady" in the past, to avoid certain races or
multiple-dispatch errors.
A "promise" is a type of "signal" that is initially "unresolved" and
is then either "resolved" with a "value" or "broken" with an error or
exception (not sure which yet). With a "promise", "when" is extended
to accept both a callback and an errback.
A "binding" is a type of "signal" that always has a value but can be
observed for changes to that value.
A "reactor" is an event loop. It would make sense in JavaScript for
this to implement "enqueue" or "defer" and "setTimeout" and
"setInterval" if timers are available.
This and other madness at https://cixar.com/svn/codish/defs/
I'm not recommending that all of this be crammed into the promise or Q
API, but rather that this is the ecosystem of nomenclature that I
would graze for these API's. I imagine actually that we could have a
four-layer architecture, not necessarily in this order:
- event
- promise
- q
- reactor
Kris Kowal
Kris Kowal wrote:
> On Sun, May 31, 2009 at 8:27 PM, Kris Zyp <kri...@gmail.com> wrote:
>
>> I was kind of hoping they both would win.
>>
>
> Oh, I see.
>
>
>> Like I had mentioned earlier,
>> they aren't mutually exclusive.
>>
>
> My apologies, I have not yet begun to discuss promises myself, being
> not yet qualified, which is a problem for which I suspect many of us
> have refrained from the discussion. But, given that I've come to
> believe they're the right road to solving perennial problems, I'm
> beginning to do my part to learn about them and in turn teach others,
> as we must rapidly do given that unlike recursion, pointers, and
> object-oriented programming, promises are not the bread and butter of
> every fresh college grad.
>
>
>> There is a proposal for the API for the
>> promises themselves (which I referenced) and the API for creating and
>> routing promises (the "Q" API which you implemented). Is that not the
>> route you would like to see this go?
>>
>
> I think I would. I would like to see a more friendly face on the Q
> API, and if that can be done with an abstraction layer, I'm all for
> it. Given a quick glance, I actually prefer "when" over
> "addCallbacks" though.
>
I certainly prefer shorter syntax as well, but my general intent is to
try to preserve maximum compatibility and AFAICT, the majority of
existing code in JavaScript that uses promises is written using Dojo's
promises, which use addCallback(s). I suppose it is true that
implementors could alias Dojo's functions if they wanted to preserve
compatibility though. However, if you like four letter words, wouldn't
"then" make more sense when used as a method name on a promise? "when"
works grammatically as a static function where the promise comes after
the function name (as in Q), but when the function name comes after the
promise, it seems like "then" reads better.
Kris
Ah, I see. Perhaps we could do the one we like and support the one
that's backward compatible.
Reading like English isn't all that important to me. As I've
mentioned, I think that "Codish" is or should be only loosely inspired
by "English"; Codish should be a language where a single word has a
single meaning, a single antonym for each dimension of its meaning, a
single grammatical function ("verb", "noun"), correspond to a single
class of names (as "play", "pause", "resume", "stop" are orthogonal
with "begin" and "end"), with no mixed metaphors and no synonyms.
I think "when" has the proper meaning, and we're accustomed to knowing
that "eq(a, b)" and "a.eq(b)" are equivalent, apart from a potential
distinction in their polymorphic behavior, so I don't think that
Q.when(promise, observer) being equivalent to promise.when(observer)
at a higher level of abstraction is that much of a stretch.
Kris Kowal
Dojo's promises came from MochiKit (http://www.mochikit.com/) ---
Python-inspired JavaScript library by Bob Ippolito (
http://bob.pythonmac.org/). So in a sense promises are standing on the
shoulders of giants. :-)