When I spoke at the Mountain View JS meetup about upcoming features in JS the other week, I showed a slide demonstrating how you can build cooperative threads with generators, in order to do asynchronous I/O in sequence without having to chain callbacks. I got a great question about this: is it possible to do several operations concurrently but then block until they all finish? Or do you have to force every I/O operation into a sequence? In other words, can you implement joins?
So I figured it was time to make my jstask library a little more fully-featured. I’ve implemented a task scheduler, so that tasks don’t have to be manually resumed. And I’ve implemented join, so that you can chain some operations, but still run others concurrently. For example:
spawn(function() {
var foo, bar, baz;
yield join(spawn(function() { foo = yield read("foo.txt"); }),
spawn(function() { bar = yield read("bar.txt"); }),
spawn(function() { baz = yield read("baz.txt"); }));
// ...
});
I’ve designed the library so that if a task throws an exception and another task is joined on it, the exception propagates to the joiner. So you can simply write:
spawn(function() {
var foo, bar, baz;
try {
yield join(spawn(function() { foo = yield read("foo.txt"); }),
spawn(function() { bar = yield read("bar.txt"); }),
spawn(function() { baz = yield read("baz.txt"); }));
} catch (e) {
// ...
}
// ...
});
If any one of the subtasks throws an exception, the others are automatically cancelled and the exception propagates to the outer task.
This might not be the only policy that makes sense. I hope to keep experimenting with jstask, and it’s up on github and available for forking (no pun intended). Maybe we’ll discover other useful policies. This is one of the cool things about generators: they’re general and flexible enough to support multiple different libraries, policies and applications.
You say that if a subtask throws an exception, the others are automatically cancelled. What does that entail? If the other taks have some cleanup to perform (e.g. cancel a pending XMLHttpRequest), how does that work?
Have you seen Stratified JavaScript (http://onilabs.com/presentations/OSCON2010/, crossbrowser-implementation: http://onilabs.com/docs)?
It has a fork-join composition construct that takes the form:
waitfor { A } and { B } and { C }
As in jstask, if any of the tasks (or ‘strata’) A, B or C throw an exception, the others will be cancelled. This cancellation can be caught by try/retract block. So if A is something like:
var request = new XMLHttpRequest();
…
try {
send_request_and_await_reply(request);
}
retract {
request.cancel();
}
…
and B throws an exception before the request is complete, then the request will automatically get cancelled.
In addition to fork-join, StratifiedJS also has an “alt composition” construct inspired by the Orc calculus (http://orc.csres.utexas.edu/):
waitfor { A } or { B } or { C }
This completes as soon as any of A,B or C complete and cancels the remaining still pending strata.
Good question! I’ve set it up so that a cancelled task is given the opportunity to do cleanup via
finallyblocks. The way this works is, the generator representing the task kernel is shut down via the generator’sclose()method, which interrupts the body as if via an exception, so all pendingfinallyblocks are executed.I haven’t really fleshed out the I/O API’s (so far I only have a simple
readandsleepas proofs of concept) to do proper finalization.No, thanks for the link. It sounds like Stratified JS uses a compiler. In my case, I’ve just written a library, which only relies on generators. Generators already exist in SpiderMonkey (i.e., in Firefox), and are a good candidate for standardization for the next version of the ECMAScript standard.
Thanks for the reference!
Dave
PS Looking at the examples, I think it would be good to add another finalization mechanism for I/O libraries to register themselves with, so that users of e.g.
readdon’t have to writefinallyblocks to cancel the underlying XHR.Nice slides! I’ll experiment with incorporating some of the Stratified JavaScript abstractions into jstask.
Thanks again,
Dave
> PS Looking at the examples, I think it would be good to add another finalization
> mechanism for I/O libraries to register themselves with, so that users of e.g. read
> don’t have to write finally blocks to cancel the underlying XHR.
In StratifiedJS, where have the luxury of being able to add new keywords to JS, so we implemented a “using” construct that does this – see http://onilabs.com/stratifiedjs#using .
It is similar to Python’s “with”, but of course “with” is already taken in JS
> Nice slides! I’ll experiment with incorporating some of the Stratified JavaScript
> abstractions into jstask.
Looking forward to seeing that!
OT cond
var x = cond {
case y > 100 { “huge” }
case y > 50 { “big” }
default { “small” }
}
// suggestion
var x = ?? {
y > 100 : “huge”,
y > 50 : “big”,
… : “small”
}
n.b – comma delimits ??cond parts as with object literals. If comma is problematic maybe use semi-colon.
RE cond:
Looks like there are plans for ?? – and as default is a keyword (and more readable than the … rest operator):
// revised suggestion
var x = ?: {
y > 100 : “huge”,
y > 50 : “big”,
default: “small”
}
Rationale:
1. The ?: expresses a relationship to the existing ?: construct
2. Succinct and readable. Easy to parse. No new keywords (which could be a factor).
3. The body looks like an object literal – which is familiar. The way the comma operator works in object literals is familiar. ie. next part of the construct.
If a user wants to use the ‘comma’ operator:
y > 50: (a++, b + 40),
Also – is there a case for an ‘elif’ keyword:
if (x > 4) doit(x)
elif (x > 2) doSomething(x)
else doWhatever(x);
There is some overlap with a ‘cond’ operator. And i appreciate the need to be parsimonious with new keywords. However:
1. It makes code more succinct and readable (compared to chained ‘else if’).
2. It’s easy to implement.
3. If cond/?: is expression orientated then a statement focused construct would be useful.