UNPKG

44.7 kBMarkdownView Raw
1l8 0.1.43
2=========
3
4[![Build Status](https://travis-ci.org/JeanHuguesRobert/l8.png)](https://travis-ci.org/JeanHuguesRobert/l8)
5
6l8 is a modern multi-tasker for javascript. It schedules javascript tasks using
7promises and distributed actors.
8
9This is a work in progress that is not ready for production.
10See [![Build Status](https://c9.io/site/wp-content/themes/cloud9/img/logo_cloud9_small.png)](https://c9.io/jhr/l8)
11
12A task is any activity that a "normal" non-blocking javascript function cannot
13do because... javascript functions cannot block! Where functions provide
14results, tasks provide promises instead. To become tasks that can block,
15functions are broken into steps that the l8 scheduler executes.
16
17```
18l8.task ->
19 @repeat ->
20 round = random = 0
21 @step -> input "Enter a decent number to start a new game"
22 @step (r) ->
23 @continue if (r = parseInt( r)) < 10
24 random = Math.floor Math.random() * r
25 round = 0
26 @repeat ->
27 @step -> input "Guess a number"
28 @step (r) ->
29 round++
30 r = parseInt( r)
31 if r > random then printnl "#{r} is too big"
32 if r < random then printnl "#{r} is too small"
33 if r is random
34 cls()
35 printnl "Win in #{round} rounds! Try again"
36 @break
37# extracted from test/input.coffee
38```
39
40What is it?
41===========
42
43This is a library to help those who want to embrace the Promise/A style of
44asynchronous programming but feel that the classic thread/blocking-function
45model is also very readable.
46
47l8 schedules the execution of multiple "tasks". A task is made of "steps", much
48like a function is made of statements. Steps are walked on multiple "paths".
49Such tasks and paths (sub-tasks) can nest, like blocks of statements.
50
51Execution goes from "step" to "step", steps are closures. If one cannot walk a
52step immediately, one does block, waiting for something before resuming.
53
54l8 tasks are kind of user level non preemptive threads. They are neither
55native threads, nor worker threads, nor fibers nor the result of some CPS
56transformation. Just a bunch of cooperating closures. However, if you are
57familiar with threads, l8 tasks should seem natural to you.
58
59l8 tasks are also "promises". Once a task is completed, it's promise is either
60fullfilled or rejected depending on the task success or failure.
61
62The main flow control structures are the sequential execution of steps, the
63execution and join of forked steps on parallel paths, steps that loop until
64they exit, steps that wait for something, error propagation similar to
65exception handling and synchronisation using the usual suspects (semaphores,
66mutexes, reentrant locks, message queues, ports, signals, generators...).
67
68When compared to callbacks, steps add some overhead due to the fact that
69what to do next is computed (based on pre-queued steps) instead of being
70specified by the callback itself. The overhead is small (see test_11 in the
71test suite) considering the extra features provided (ie. nesting, cancelation,
72tasks hierarchie, etc). When that overhead is useless, one can revert to the
73classic callback style, ie. blocking and callback modes intermix well.
74
75Steps vs Statements
76===================
77
78Javascript code is made of statements (and expressions). One key characteristic
79of the language is the fact that all these statements are "non blocking". This
80means that a statement cannot "block". It is executed with no delay, it cannot
81"wait" for something to happen.
82
83As a result there is only one "thread" of execution and any activity that
84cannot complete immediately needs to register code to execute later when
85some "event" occurs. This single thread runs a tight loop that consumes events
86and run code registered to handle them. This is "the event loop".
87
88```
89 while( true ){
90 event = get_next_event();
91 dispatch( event);
92 }
93```
94
95Code that is executed when an event happens is often named "callback". This is
96because it is the "event loop" (though "dispatch()") that "calls back" that
97code.
98
99```
100 function process_mouse_over(){
101 obj.onmouseover = call_me_back;
102 function call_me_back(){
103 // called when mouse runs over that obj
104 }
105 }
106```
107
108That event_loop/callback style is simple and efficient. However, it has some
109notorious drawbacks. Things can get fairly complex to handle when some activity
110involves many sub-activities that must be run in some specific order.
111
112Multiple solutions exist to care with such cases. The most basic one is to
113start a new activity from within the callback that gets called when the
114previous activity is completed.
115
116```
117 ajax_get_user( name, function user_found( user ){
118 ajax_check_credential( user, function credential_checked( is_ok ){
119 if( is_ok ){
120 ajax_do_action( user, "delete", function delete_result( err ){
121 if( err ) signal( err)
122 }
123 }
124 }
125 }
126```
127This code is not very readable because of the "nesting" of the different parts
128that obscures it.
129
130```
131 ajax_get_user( name, user_found);
132 function user_found( user ){
133 ajax_check_credential( user, credential_checked);
134 }
135 function credential_checked( is_ok ){
136 if( !is_ok )return
137 ajax_do_action( user, "delete", delete_result)
138 }
139 function delete_result( err ){
140 if( err ) signal( err)
141 }
142```
143This slightly different style is barely more readable. What would be readable
144is something like this:
145
146```
147 var user = ajax_get_user( name);
148 if( !ajax_check_credential( user) ) return;
149 if( err = ajax_do_action( user, "delete") ) signal( err);
150```
151
152However, this cannot exist in javascript because no function can "block". The
153function "ajax_get_user()" cannot "block" until it receives an answer.
154
155This is where l8 helps.
156
157Steps
158-----
159
160Steps are to Tasks what statements are to functions: a way to describe what
161they do.
162
163```
164 var user;
165 l8
166 .step( function( ){ ajax_get_user( name) })
167 .step( function( result ){ ajax_check_credential( user = result) })
168 .step( function( result ){ if( !result ) l8.return();
169 ajax_do_action( user, "delete") })
170 .step( function( result ){ if( result ) signal( result) })
171```
172
173This is less verbose with CoffeeScript:
174
175```
176 user = null
177 @step -> ajax_get_user name
178 @step (r) -> ajax_check_credential (user = r)
179 @step (r) -> if !r then @return() else
180 ajax_do_action user, "delete"
181 @step (r) -> if err = r then signal err
182```
183
184By "breaking" a function into multiple "steps", code become almost as readable
185as it would be if statements in javascript could block, minus the "step" noise.
186
187This example is a fairly simple one. Execution goes from step to step in a
188sequential way. Sometimes the flow of control is much more sophisticated.
189There can be multiple "threads" of control, with actions initiated concurrently
190and various styles of collaboration between these actions.
191
192Please note that the ajax_xxx() functions of the example are not regular
193functions, they are "task constructors". When you invoke such a function, a new
194task is created. See below.
195
196If they were usual ajax_xxx( p1, p2, cb) style of functions, one would need to
197use .walk or .proceed() as the callback in order to ask l8 to move to the next
198step:
199
200```
201 var user = null, err
202 this.step( function( ){ ajax_get_user( name, this.walk ) }
203 this.step( function( r ){ ajax_check_credential( (user = r), this.walk ) }
204 this.step( function( r ){ if( !r ) this->return()
205 ajax_do_action( user, "delete", this.walk ) }
206 this.step( function( r ){ if( err = r ) signal( err)
207```
208
209Tasks
210-----
211
212About the notion of "task". A Task is a l8 object that consolidates the result
213of multiple threads of control (aka sub-tasks) that all participate in the
214completion of a task.
215
216Tasks are to steps what functions are to statements: a way to group them.
217
218To perform a task, the simplest way is to invoke a "task constructor". It will
219schedule the new task and return a Task object. Such an object is also a
220"Promise". This means that it is fairly easy to get notified of the task's
221completion, either it's success or it's failure.
222
223```
224 var new_task = do_something_task()
225 new_task.then( on_success, on_failure)
226 function on_success( result ){ ... }
227 function on_failure( reason ){ ... }
228```
229
230A "task constructor" is to a "task" what a "function" is to a "function call":
231both define (statically) what happens when they are invoked (dynamically).
232
233Tasks queue steps that the l8 scheduler will execute much like functions queue
234statements that the Javascript interpretor executes. With functions, statements
235queueing is implicit. With tasks, it becomes explicit. As a result, defining
236what a task does is of course sligthly less syntaxically easy.
237
238```
239 do_something_task = l8.Task( do_something_as_task );
240 function do_something_as_task(){
241 this
242 .step( function(){ this.sleep( 1000) })
243 .fork( function(){ do_some_other_task() })
244 .fork( function(){ do_another_task() })
245 .step( function(){ ... })
246 }
247```
248
249This is the "procedural" style. A "declarative" style is also available where
250what is usually a function can be a list of steps:
251
252```
253 do_something_task = l8.Task(
254 function(){ this.sleep( 1000) },
255 {fork: function(){ do_some_other_task() }},
256 {fork: function(){ do_another_task() }},
257 [
258 {step: function(){...}},
259 {failure: function(){...}}
260 ],
261 {repeat:[
262 function(){ do_something },
263 function(r){ if( !r ) this.break }
264 {failure: function(){ ... }}
265 ]}
266 {success: function(){ ... }},
267 {final: function(){ .... }}
268 )
269```
270
271There is also a trans-compiler option that takes a funny looking function and
272turns it into a task constructor. It's compact but you lose the ability to set
273break-points in a debugger.
274
275```
276 do_something_task = l8.compile( do_something_as_task );
277 function do_something_as_task(){
278 step; this.sleep( 1000);
279 fork; do_some_other_task_xx();
280 fork; another_task_xx();
281 step( a, b ); use_xx( a); use_xx( b);
282 begin
283 ...
284 step; ...
285 failure; ...
286 end
287 repeat; begin
288 ...
289 step; act_xx()
290 step( r ); if( !r ) this.break
291 end
292 success( r ); done_xx( r);
293 failure( e ); problem_xx( e);
294 final( r, e); always_xx();
295 }
296```
297
298Note that when do_something_task() is called, it does not do the actual work,
299it only registers future steps. These steps, and steps later added to the task,
300are executed later, in the appropriate order, until the task is fully done.
301It is then, and only then, that the on_success/on_failure callbacks of the
302task's promise will be called.
303
304In a function, statements are executed in a purely sequentiel order. That
305restriction does not apply with steps in a task. While the sequential order
306is still the "normal", steps that run in parallel paths can also exist. Such
307steps can be the result of "forks". When all forks are done, the forks "join"
308and execution continues with the next normal step. When using a generator, the
309steps of the producer and those of the consumer are executed alternatively when
310.yield() and .next() are called to handle a new generated results.
311
312Promises
313--------
314
315Promises are to tasks what result/exception are to a function: a way to provide
316information about the outcome.
317
318The "de facto" current standard for promises is part of the CommonJS effort:
319http://wiki.commonjs.org/wiki/Promises/A
320
321Such a "promise" is any object that provides a "then" method. That method does
322two things: it registers callbacks to call when the promise is either, later or
323now, fullfilled or rejected and it also returns a new promise that will be
324fullfilled or rejected depending on the result of these callbacks; this makes
325chaining easy.
326
327Please note that "promises" in the Javascript world is not a mature feature.
328The "de facto" CommonJS standard is challenged by another "de facto" strong
329strong contender: jQuery. Their implementation of then() differs significantly
330regarding chaining and exception handling. l8.wait() does not use these
331features and consequently l8.wait() should work with most implementations,
332including jQuery's one.
333
334One can invoke .then() multiple times on the same promise. When that promise is
335either fullfilled or rejected, all the registered callbacks are processed.
336
337Some features of l8 that involve promises require a promise factory. l8 can use
338the factories of Q.js, When.js, Angular.js, etc. The factory must return a new
339object that supports .resolve(), .reject() and a .promise() that returns an
340object that supports a Promise/A compliant .then().
341
342Generators
343----------
344
345Generators are subtasks that provide a result in multiple pieces instead of in
346just one piece as regular tasks do. Such a task is a "producer" of results,
347some other task, often the one that spawn the generator, is the "consumer" of
348these results.
349
350Consumers usually consume the next result that some subtask yields until the
351generator reaches an end and is closed, either by the producer or the consumer.
352
353l8.Generator( block) builds a "Generator Constructor" much like l8.Task( block)
354does with "Task Constructor". When the constructor is invoked, a generator task
355is spawn. That task uses .yield() to produce results. On the consumer side, the
356task uses .next([opt]) to get that result and optionaly provide a hint about
357future results.
358
359```
360 var fibonacci = L4.Generator( function(){
361 var i = 0, j = 1;
362 this.repeat( function(){
363 this.yield( i);
364 var tmp = i;
365 i = j;
366 j += tmp;
367 })
368 })
369
370 var gen = fibonacci()
371 var count_down = 10
372 this.repeat( function(){
373 this.step( function(){
374 if( !count_down-- ) this.break
375 gen.next()
376 }).step( function( r ){
377 trace( count_down, "fibo", r)
378 })
379 })
380```
381
382
383API
384===
385
386```
387 l8
388 -- step/task creation. "body" can create additional steps/subtasks
389 .step( body ) -- queue a step on the path to task's completion
390 .task( body ) -- queue a step that waits on a blocking subtask
391 .fork( body ) -- queue a step that starts a forked task, forks "join"
392 .repeat( body ) -- queue a step that repeats a blocking subtask
393 .spawn( body ) -- like fork() but next step does not wait for subtask
394 .generator( body ) -- queue a step that spwans a task that yields results
395
396 -- step walking
397 .proceed( block ) -- walk a step on its path, at most once per step
398 .walk -- idem but params of block become results of step
399 .flow -- idem but first param is filtered out unless thrown
400 .continue -- stop executing current task, reschedule it instead
401 .break -- "break" for "repeat" steps
402 .return( [val] ) -- like "return" in normal flow, skip all queued steps
403 .raise( error ) -- raise an exception in task, skip all queued steps
404
405 -- task completion monitoring, for task users
406 .then( ... ) -- Promise/A protocol, tasks are promises
407 .callback( cb ) - - Node.js style callback. Also .callback( promise, cb)
408 .join() -- pause task until all subtasks are done
409
410 -- task completion handling, for task implementers
411 .defer( body ) -- push a block to execute when task is almost done
412 .progress( block ) -- block to run when a subtask is done or step walked
413 .success( block ) -- block to run when task is done without error
414 .failure( block ) -- block to run when task is done but with error
415 .final( block ) -- block to run when task is all done (after .defer())
416
417 -- task "local" variables, subtasks inherit them, a binding store them
418 .var( name, val ) -- define a new variable in current task's binding
419 .get( name ) -- get value of task local variable
420 .set( name, val ) -- set value of task local variable
421 .binding( name ) -- return binding where task local variable is stored
422
423 -- task state related
424 .state -- return state of task, I->[Run|Pause]*->Success/Fail
425 .pause -- block task at step, waiting until task is resumed
426 .paused -- return true if task was paused
427 .resume -- resume execution of task paused at some step
428 .running -- true if task not done nor paused
429 .cancel -- cancel task & its sub tasks, brutal
430 .canceled -- true if task failed because it was canceled
431 .stop -- gentle cancel
432 .stopping -- true after a gentle cancel, until task is done
433 .stopped -- true if done task was gently canceled (gracefull)
434 .done -- true if task done, else either running or paused
435 .succeed -- true if task done without error
436 .fail -- true if task done but with an error
437 .error -- last raised error (ie last exception)
438 .result -- result of last successful step
439 .timeout( milli ) -- cancel task if it is not done on time
440 .sleep( milli ) -- block on step for a while, then move to next step
441 .wait( promise ) -- block task until some lock opens, promise agnostic
442
443 -- misc, hierarchy
444 .l8 -- return global L8 object, also root task
445 .current -- return current task
446 .parent -- return parent task
447 .tasks -- return immediate pending sub tasks
448 .top -- return top task of sub task (child of l8 root task)
449
450 -- scoping (value of "this" related)
451 .begin -- create a new task
452 .end -- start that new task
453 .Task( function ) -- the .begin/.end guarded version of a function
454
455 All these methods, if invoked against the global l8 object, will usually get
456 forwarded to the "current task", the task that is currently executing. That
457 task is often the returned value of such methods, when it makes sense.
458
459 To synchronize the access to resources, l8 provide a few well known basic
460 solutions implemented using promises and invoked using task.wait( resource):
461
462 .semaphore( [n] ) -- create a new semaphore, also a promise provider
463 .mutex( [entered] ) -- ... a new mutex, also a ...
464 .lock( [nentered] ) -- ... lock (reentrant mutex), ...
465 .queue( [bound] ) -- message queue, ...
466 .port() -- like a message queue but without any buffering
467 .signal() -- signal, ..., like a promise that fires many times
468 .timeout( delay ) -- a promise fulfilled after a delay
469 .generator() -- a next()/yield() consumer/producer resource
470 .Generator( block ) -- build a Generator Constructor.
471
472 Semaphores, mutexes and locks provide:
473
474 .promise -- provide a promise fullfilled when rsrc is acquired
475 .release() -- make resource available
476 .close() -- reject pending promises
477 .task -- resource owner task, when applicable (mutex & lock)
478
479 Message queues are useful to synchronize a consumer and a producer:
480
481 .in -- a "can get()" promise, alias for .promise
482 .out -- a "can put()" promise
483 .get() -- pause current task until queue is not empty, get msg
484 .tryGet() -- get msg when one is available, don't block
485 .put( msg ) -- pause current task until queue is not full, put msg
486 .tryPut( msg ) -- put msg in queue unless queue is full
487 .capacity -- total capacity (bound)
488 .length -- used capacity
489 .full -- when capacity is totally used
490 .empty -- when length is 0
491
492 Signals are usefull to send a signal to multiple tasks when some condition is
493 met:
494
495 .promise -- a promise fullfilled when signal is next signaled
496 .signal( value ) -- signal signal, resolve all pending promises
497
498 Generators let a producer and a consumer collaborate in a next()/yield() way:
499
500 .get -- a "can next()" promise, alias for .promise
501 .put -- a "can yield()" promise
502 .next( [msg] ) -- pause task until producer yields, get/send a msg
503 .yield( msg ) -- pause task until consumer calls .next(), get/send
504 .tryNext( [msg] ) -- if .get promise is ready, get yield's msg
505 .tryYield( msg ) -- if .put promise is ready, get next's msg
506 .close() -- break paused tasks (using .break())
507 .closed -- true once generator is closed
508
509 When a producer task is created using a Generator Constructor, that task can
510 use l8.yield() while the parent task can use l8.next() ; the associated
511 generator will automatically get closed when either the producer or the
512 consumer task terminates.
513
514 Many actions are possible when you have a hand of promises, l8 provides some
515 of them:
516
517 .selector( promises ) -- fires when any promise does
518 .any( promises ) -- alias for .selector()
519 .or( promises ) -- fires when a promise with a non falsy result fires
520 .aggregator( promises) -- collect results, fires when all promises did
521 .all( promises ) -- alias for .aggregator()
522 .and( promises ) -- fires with "false" early or with collected results
523 Note: in addition to promises, the array can contain immediate values and
524 functions returning either an immediate value, a function to evaluate or a
525 promise. The result of a promise can be a Function that will be evaluated and
526 will replace the initial promise.
527
528 Additional librairies provides other usefull services. See Q.js, When.js,
529 Promise.io, etc
530
531 .
532
533 Misc:
534 .debug( [on]) -- get/set debug mode
535 .trace( p1, ... ) -- output trace
536 .de -- my de&&bug darling
537
538```
539
540Simple example, explained
541=========================
542
543Two steps. Hypothetical synchronous version if functions could block:
544
545```
546 function fetch( a ){
547 meth1( a)
548 return meth2( a)
549 }
550```
551
552Idem but actual javascript using callback style:
553
554```
555 function fetch( a, cb ){
556 meth1( a, function( error, result ){
557 if( error ) return cb( error);
558 meth2( a, function( error, result ){
559 cb( error, result);
560 }
561 }
562 }
563```
564
565Idem but using l8, extra long version:
566
567```
568 function fetch_this_and_that( a, callback ){
569 return l8.begin
570 .step( function(){
571 meth1( a, this.next ) })
572 .step( function( err, result ){
573 if( err ) throw err else meth2( a, this.next }) })
574 .step( function( err, result ){
575 if( err ) throw err else return result })
576 .final( function( err, result ){ callback( err, result) })
577 .end}
578```
579
580CoffeeScript, much shorter, also thanks to Task() functor:
581
582```
583 fetch = l8.Task (a,cb) ->
584 @step -> meth1 a, @walk
585 @step (e,r) -> if e then throw e else meth2 a, @walk
586 @step (e,r) -> if e then throw e else r
587 @final (e,r) -> cb e, r
588```
589
590Idem but returning a promise instead of using a callback:
591
592```
593 fetch = l8.Task (a) ->
594 @step -> meth1 a, @walk
595 @step (e,r) -> if e then throw e else meth2 a, @walk
596 @step (e,r) -> if e then throw e else r
597```
598
599Idem but assuming meth1 and meth2 make tasks returning promises too:
600
601```
602 fetch = l8.Task (a) ->
603 @step -> meth1 a
604 @step -> meth2 a
605```
606
607Back to Javascript:
608
609```
610 fetch = l8.Task( function( a ){
611 this.step( function(){ meth1( a) })
612 this.step( function(){ meth2( a) })
613 })
614```
615
616Using the "trans-compiler":
617
618```
619 fetch = l8.compile( function( a ){
620 step; meth1( a);
621 step; meth2( a);
622 })
623```
624
625The conclusion is that using tasks, steps and promises, the code's structure is
626similar to the hypothetical javascript blocking function.
627
628Other examples
629--------------
630
631Multiple steps, run sequentially:
632
633```
634 fetch_all_seq = l8.Task (urls) ->
635 results = []
636 for url in urls then do (url) ->
637 @step -> scrap url, @proceed -> result.push {url, err, content}
638 @success -> results
639```
640
641Multiple steps, each run in parallel:
642
643```
644 fetch_all = l8.Task (urls) ->
645 results = []
646 for url in urls then do (url) ->
647 @fork ->
648 scrap url, @proceed (err, content) -> results.push {url, err, content}
649 @success -> results
650```
651
652Repeated steps, externally terminated, gently:
653
654```
655 spider = l8.Task (urls, queue) ->
656 @repeat ->
657 url = null
658 @step -> url = queue.shift
659 @step -> @delay 10000 if @parent.tasks.length > 10
660 @step ->
661 @break if @stopping
662 scrap url, @walk
663 @step (err,urls) ->
664 return if err or @stopping
665 for url in urls
666 queue.unshift url unless url in queue
667
668 spider_task = l8.spawn -> spider( "http://xxx.com")
669 ...
670 stop_spider = -> spider_task.stop
671```
672
673Small loop, on one step, using "continue":
674
675```
676 fire_all = l8.Task (targets) ->
677 ii = 0
678 @step ->
679 return if ii > targets.length
680 targets[ii++].fire()
681 @continue
682```
683
684StratifiedJs example, see http://onilabs.com/stratifiedjs
685
686```
687var news;
688waitfor {
689 news = http.get("http://news.bbc.co.uk");
690}
691or {
692 hold(1000);
693 news = http.get("http://news.cnn.com");
694}
695or {
696 hold(1000*60);
697 throw "sorry, no news. timeout";
698}
699show(news);
700```
701
702The equivalent code with l8 is:
703
704```
705
706// JavaScript
707var show_news = l8.Task( function(){
708 var news = this
709 .fork( function(){ http.get( "http://news.bbc.co.uk",
710 this.proceed( function( item ){ news.return( item) }) )
711 })
712 .fork( function(){
713 this.step( function(){ this.sleep( 1000) });
714 this.step( function(){ http.get( "http://news.cnn.com",
715 this.proceed( function( item ){ news.return( item) }) )
716 })
717 })
718 .fork( function(){
719 this.step( function(){ this.sleep( 1000 * 60) });
720 this.step( function(){ throw "sorry, no news. timeout" })
721 })
722 .success( function( news ){ show( news) });
723})
724
725// CoffeeScript
726show_news = l8.Task ->
727 news = @current
728 @fork ->
729 @step -> http.get "http://news.bbc.co.uk"
730 @step -> @news.return()
731 @fork ->
732 @step -> @sleep 1000
733 @step -> http.get "http://news.cnn.com"
734 @step -> @news.return()
735 @fork ->
736 @step -> @sleep 1000 * 60
737 @step -> throw "sorry, no news. timeout"
738 @success( news ) -> show news
739
740// l8 trans-compiler
741var show_new = l8.compile( function(){
742 var news = this
743 fork; begin
744 step; http.get( "http://news.bbc.co.uk");
745 step; news.return();
746 end
747 fork; begin
748 step; this.sleep( 1000);
749 step; http.get( "http://news.cnn.com");
750 step; news.return();
751 end
752 fork; begin
753 step; this.sleep( 1000 * 60);
754 step; throw "sorry, no news. timeout";
755 end
756 success( news ); show( news);
757})
758
759```
760
761Node.js google group "pipe" example, see
762https://groups.google.com/forum/?fromgroups=#!topic/nodejs/5hv6uIBpDl8
763
764```
765function pipe( inStream, outStream, callback ){
766 var loop = function( err ){
767 if (err) callback( err);
768 else inStream.read( function( err, data ){
769 if (err) callback(err);
770 else data != null ? outStream.write( data, loop) : callback();
771 });
772 }
773 loop();
774}
775
776pipe = l8.Task( function ( inStream, outStream ){ this
777 .repeat( function(){ this
778 .step( function(){
779 inStream.read() })
780 .step( function( data ){
781 if( !data) this.break;
782 outStream.write( data);
783 })
784 })
785})
786
787pipe = l8.Task (in,out) ->
788 @repeat ->
789 @step -> in.read()
790 @step (data) ->
791 @break if !data
792 out.write data
793
794pipe = l8.compile( function( in, out ){
795 repeat; begin
796 step; in.read();
797 step( data ); if( !data ) this.break;
798 out.write( data);
799 end
800})
801```
802
803Note: for this example to work, node.js streams need to be "taskified". This
804is left as an exercize.
805
806The "recursive dir walk" nodejs challenge:
807
808```
809Var fs = require('fs');
810var path = require('path');
811
812var recurseDir = function(dir) {
813 fs.readdirSync(dir).forEach(function(child) {
814 if (child[0] != '.') {
815 var childPath = path.join(dir, child);
816 if (fs.statSync(childPath).isDirectory()) {
817 recurseDir(childPath);
818 } else {
819 console.log(childPath);
820 }
821 }
822 });
823};
824recurseDir(process.argv[2]);
825
826// Async version:
827var recurseDir = l8.Task( function( dir ){
828 l8.step( function( ){ fs.readdir( dir, this.flow) })
829 l8.step( function( l ){ l.forEach( function( child ){
830 if( child[0] != "." ){
831 var childPath = path.join( dir, child);
832 l8.step( function( ){ fs.stat( childPath, this.flow) })
833 l8.step( function( r ){
834 if( r.isDirectory() ){
835 recurseDir( childPath)
836 }else{
837 console.log(dchildPath)
838 }
839 })
840 }
841 }) })
842})
843```
844
845Cooperating tasks examples
846==========================
847
848Use promises:
849
850A step can block waiting for a promise using L8.wait( promise). If waiting for
851a promise is the only action of a step, then L8.step( promise) can be used as
852a shortcut for L8.step( function(){ L8.wait( promise) }). Note however that the
853two forms are not semantically identical because L8.step( promise) uses the
854promise available when the new step is created/scheduled whereas in the second
855form L8.wait() uses the promise available when the step is actually executed,
856not when it is scheduled.
857
858Access to a critical resource:
859
860```
861var mutex = L8.mutex()
862 ...
863 .step( mutex) // or .step( function(){ this.wait( mutex) })
864 .step( function(){
865 l8.defer( function(){ mutex.release() })
866 xxx
867 })
868 ...
869```
870
871Producer/consumer:
872
873```
874 TBD
875```
876
877
878Mixing statements and steps
879---------------------------
880
881Because "steps" and "statements" are not on the same level (steps for tasks,
882statements for functions), the classical javascript control structures have
883equivalent structures at the step level.
884
885```
886function xx(){
887 ..1..
888 try{
889 ..2..
890 catch( e ){
891 ..3..
892 finally {
893 ..4..
894 }
895 ..5..
896}
897```
898becomes:
899
900```
901xx_task = l8.Task( function(){
902 this.step( function(){
903 ..1..
904 }).step( function(){
905 this.begin.step( function(){
906 ..2..
907 }).failure( function(e){
908 ..3..
909 }).final( function(){
910 ..4..
911 }).end
912 }).step( function(){
913 ..5..
914 })
915})
916
917or
918
919xx_task = l8.compile( function(){
920 step; ..1..
921 step; begin
922 ..2...
923 failure;
924 ..3..
925 final;
926 ..4..
927 end
928 step; ..5..
929})
930```
931
932```
933while( condition ){
934 ...
935 if( extra )break
936 ...
937 if( other_extra )continue
938 ...
939}
940```
941becomes:
942
943```
944l8.repeat( function(){
945 ...
946 if( condition ) this.break
947 ...
948 if( extra ) this.break
949 ...
950 if( other_extra ) this.continue
951 ...
952}
953
954or
955
956xx = l8.compile( function(){
957 repeat; begin
958 ...
959 if( condition ) this.break
960 ...
961 if( extra ) this.break
962 ...
963 if( other_extra ) this.continue
964 ...
965 end
966})
967```
968
969```
970for( init ; condition ; next ){
971 ...
972}
973```
974
975becomes:
976
977```
978 init
979 this.repeat( function(){
980 if( condition ) this.break
981 ...
982 next
983 })
984```
985
986```
987for( init ; condition ; next ){
988 ...
989 if( extra ) continue
990 ...
991})
992```
993becomes:
994
995```
996 init
997 this.repeat( function(){
998 if( condition ) this.break
999 this.task( function(){
1000 ...
1001 this.step( function(){ if( extra ) this.return })
1002 ...
1003 this.step( function(){ next })
1004 })
1005 })
1006
1007or
1008
1009xx = l8.compile( function(){
1010 init; repeat; begin ; if( condition ) this.break ; begin
1011 ...
1012 if( extra ) this.return
1013 ...
1014 end; next; end
1015})
1016```
1017
1018```
1019for( var key in object ){
1020 ...
1021}
1022```
1023becomes:
1024
1025```
1026 var keys = object.keys(), key
1027 this.repeat( function(){
1028 if( !(key = keys.shift()) ) this.break
1029 ...
1030 })
1031
1032or
1033
1034xx = l8.compile( function(){
1035 var keys = object.keys(), key
1036 repeat; begin;
1037 if( !(key = keys.shift()) ) this.break
1038 ...
1039 end
1040})
1041```
1042
1043.defer() versus .final()
1044========================
1045
1046.final( block) provides a solution that mimics the finally clause of the "try"
1047javascript construct. The final block typically performs "clean up" work
1048associated with the task it is attached too.
1049
1050```
1051 var file
1052 l8
1053 .step( function(){ file = file_open( file_name) })
1054 .step( function(){ xxxx work with that file })
1055 .final( function(){ file_close( file) })
1056```
1057
1058There is only one final clause per task. That clause is attached to the task
1059when the .final() method is executed. When multiple clauses are needed, one
1060needs to create nested tasks. The final block is executed once the task is
1061done. As a result additional steps are attached to the "parent" task, not to
1062the current task (this may change in a future version).
1063
1064```
1065 var file1
1066 var file2
1067 l8
1068 .step( function(){ file2 = file_open( file1_name) })
1069 .step( function(){ xxxx work with file1 xxx })
1070 .step( function(){
1071 if( some_thing ){ l8.begin
1072 .step( function(){ file = file_open( file2_name) })
1073 .step( function(){ xxx work with file2 xxx })
1074 .final( function(){ file_close( file2) })
1075 .end })
1076 .final( function(){ file_close( file1) })
1077```
1078
1079.defer( block) is inspired by the Go language "defer" keyword. It is itself
1080a variation around the C++ notion of "destructors". There can be multiple
1081deferred blocks for a task. Because deferred steps are executed just before the
1082task reach its end, they can register additional steps to handle async
1083activities. As a result, the task is not fully done until all the defered work
1084is done too. Deferred blocks are executed in a LIFO order, ie the last deferred
1085step is run first.
1086
1087```
1088 var resourceA
1089 var resourceB
1090 l8
1091 .step( function(){ acquireResource( xxx) })
1092 .step( function( r ){
1093 ressourceA = r
1094 l8.defer( function(){ releaseResource( resourceA) })
1095 })
1096 .step( function(){ xxx work with resourceA xxx })
1097 .step( function(){ acquireResource( yyy) })
1098 .step( function( r ){
1099 resourceB = r
1100 l8.defer( function(){ releaseResource( resourceB) })
1101 })
1102 .step( function(){ xxx work with resourceB xxx })
1103```
1104
1105Because multiple deferred blocks are possible, .defer() is more modular. For
1106example, it makes it possible to implement the "Resource Acquisition is
1107Initialization" pattern.
1108See http://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization
1109
1110```
1111 var with_file = l8.Task( function( file_name ){
1112 var file
1113 l8
1114 .step(){ function(){ file_open( file_name) })
1115 .step( r ){
1116 file = r
1117 l8.parent.defer( function(){ file_close( file) })
1118 }
1119 })
1120
1121 Usage:
1122
1123 var file
1124 l8
1125 .step( function(){ with_file( file_name) })
1126 .step( function( r ){ xxx work with file r xxx })
1127 xxx don't worry about closing that file xxx
1128```
1129
1130The general "rule of thumb" is to use .final() for quick & simple stuff and
1131use .defer() for more elaborated async stuff.
1132
1133
1134Task "local" variables
1135======================
1136
1137Tasks can define variables much like functions can. There are some differences.
1138Contary to function local variables, task local variables are "fluid", as per
1139Scheme jargon, ie they are dynamically scoped (whereas javascript variables use
1140lexical scoping). See also http://en.wikipedia.org/wiki/Thread_local_storage
1141
1142A nice property of task local variables is the fact that a variable defined by
1143a parent task is accessible from a child subtask. As a result, task local
1144variables are "global" to a subset of all tasks, based on the task hierarchy.
1145
1146When a subtask needs to override an inherited variables, it uses ".var()" to
1147set a new value that it's own subtasks will share. When a subtask, on the
1148contrary, wants to share an inherited variables, it uses ".set()" to set a new
1149value that it's parent task can query using ".get()".
1150
1151Please note that tasks can also use regular lexically scoped variables, as long
1152as such a variable is part of a function's closure. This is the most convenient
1153and fast use case. When more global variables are required, l8 fluid variables
1154are handy.
1155
1156```
1157var trace = function(){
1158 l8.trace( l8.get( "message") + " from " + l8.binding( "message").task)
1159}
1160var subtask = function(){
1161 l8.label = "sub"
1162 l8.step( function(){ trace() })
1163 l8.step( function(){ l8.var( "message", "deeper") })
1164 l8.step( function(){ l8.delay( 10) })
1165 l8.step( function(){ trace() })
1166}
1167
1168l8.task( function(){
1169 l8.label = "main"
1170 l8.var( "message", "top")
1171 l8.spawn( subtask )
1172 l8.step( function(){ l8.var( "message", "deeper") })
1173 l8.step( function(){ trace() })
1174})
1175
1176displays: top from Task/x[main], top from Task/x[main], deeper from Task/x[sub]
1177```
1178
1179Actors and proxies
1180==================
1181
1182Actors are objects that communicate the one with the others using messages.
1183
1184When idle, an actor simply waits for incoming messages. When a message is
1185received, the actor can either decide to process it or decide to process
1186it later, after some other messages. As a result, each actor has a message
1187queue, called a "mailbox" in the actor jargon.
1188
1189Some messages don't require an answer. They are "send type" messages. Some
1190messages do require an answer. They are "call type" messages.
1191
1192When it processes a "call type" message, the actor can decide to provide
1193either a direct response or a promise to be fullfilled (or rejected) later
1194on. Until that decision is taken, additional "call type" messages are
1195queued. This makes it easy to "serialize" the processing of calls when that
1196makes sense.
1197
1198Each actor has a unique name, provided by the actor creator. All actors are
1199remembered in a global registry where one can lookup for them. Names are
1200in the form xx.xx.xx where the last xx is generated by the actor itself
1201when only xx.xx. is provided at creation time.
1202
1203Actors can be usefull to build distributed systems. In these configurations,
1204each javascript process hosts actors and communicate with actors hosted in some
1205other processes, via proxies.
1206
1207API:
1208```
1209 .Actor( name, pattern ) -- make an Actor task constructor
1210 .send( ... ) -- send a message to the actor
1211 .receive( pattern ) -- redefine reaction to received messages
1212 .Actor.lookup( name ) -- look for an existing actor
1213 .Actor.all -- an object with one propertie per existing actor
1214 .actor -- actor the current task is running
1215 .actor.stage -- stage the actor received current message from
1216 .Role -- base class for Role objects, can be extented
1217 .role( delegate ) -- alternative mechanism to define actor's patterns
1218 .stage( name, [url] ) -- a place with actors in it
1219 .proxy( stage, name ) -- access to remote actors
1220```
1221
1222L8 Design
1223---------
1224
1225The key idea is to break a javascript function into "steps" and then walk thru
1226these steps much like the javascript interpreter runs thru the statements
1227of a function. This is quite verbose however. But not so much when using
1228CoffeeScript. This is why, after considering the idea years ago, I waited
1229until now to implement it. That my cousin Jean Vincent would consider breaking
1230a function into steps as something close enough to threading was another strong
1231motivator.
1232
1233To break functions into steps, I use a DSL (domain specific language) API.
1234Once the AST (abstact syntax tree) is built, I interpret it.
1235
1236Executable nodes in the AST are called "steps". They are the smallest non
1237interruptible executable entities. Each Step belongs to a task. Task can
1238involve sub tasks that cooperate across multiple paths.
1239
1240This becomes really interesting when the AST gets dynamically modified! This
1241happens when a step decides that it requires additional sub steps to complete.
1242
1243On a path, execution goes from step to step. When a step involves sub-steps,
1244the step is blocked until the sub-steps complete, unless sub-steps are created
1245in a forked parallel path.
1246
1247Example:
1248
1249```
1250MainTask
1251 Task.1 - a task with a single path with a loop subpath
1252 MainPath
1253 Step
1254 Step
1255 RepeatPath
1256 Step
1257 Step
1258 Step
1259 Task.2 - a task with two paths (two forked subtasks)
1260 MainPath
1261 ForkedPath
1262 Step
1263 Step
1264 ForkedPath
1265 Step
1266 Step
1267```
1268
1269Adding steps
1270------------
1271
1272Execution goes "step by step" until task completion. Steps to execute are
1273queued. To queue a new step to execute after the currently executing step, use
1274.step(). Such steps are run once the current step is completed, FIFO order.
1275
1276To insert a new step on a new parallel task/path, use .fork(). Such steps block
1277the current step until they are completed. When multiple such forked steps are
1278inserted, the next non forked step will execute when all the forked steps are
1279done. The result of such multiple steps is accumulated into an array that can be
1280the parameter of the next non forked step. This is a "join". When only one
1281forked step is inserted, this is similar to calling a function, ie the next
1282step receives the result of the task that ran the forked step. There is a
1283shortcut for that special frequent case, please use .task().
1284
1285To insert steps that won't block the current step, use spawn() instead. Such
1286steps are also run in a new task but the current step is not blocked until
1287the new task is complete. If the parent task terminates before the spawn tasks
1288are completed, the spawn tasks are re-attached to the parent task of the task
1289that created them, ie. spawn tasks are "inherited" by the parent of their
1290creator (Unix processes are similar).
1291
1292Note that is it possible to cancel tasks and/or their subtasks. That cancel
1293action can be either "gentle" (using .stop() & .stopping) or "brutal" using
1294.cancel(). a_task.return( x) also cancel a task (and it's subtasks) but
1295provides the result of that task (whereas .cancel() makes the task fail).
1296
1297Blocking
1298--------
1299
1300Steps are useful to describe flows that depends on other flows. As a result
1301a step often describes sub-steps and/or sub pathes/tasks. These steps then
1302"block" waiting for the sub items to complete.
1303
1304For simple steps, that only depend on the completion of a simple asynchronous
1305function, .walk or .proceed() provides the callback to register with that
1306function. When the callback is called, flow walks from the current step to the
1307next one.
1308
1309Note: in the frequent case where the callback only needs to store the result
1310of the asychronous operation and move forward to the next step, please use
1311"walk" instead of proceed( function( x ){ this.result = x }).
1312
1313However, if the action's result dictates that some new "nested" steps are
1314required, one adds new steps from within the callback itself. Often, this style
1315of programming is not adviced because it basically resolves back to the
1316infamous "callback hell" that l8 attemps to avoid. A better solution is to
1317let the next step handle that.
1318
1319Do:
1320```
1321 @step -> fetch @walk
1322 @step( result ) -> if result then more_fetch @walk
1323 @step( result ) -> if result then fetch_again @walk
1324 @step( result ) -> if result then use result @walk
1325 @step -> done()
1326```
1327
1328Don't:
1329```
1330 @step -> fetch @proceed (result) ->
1331 if result
1332 more_fetch @proceed (result) ->
1333 if result
1334 fetch_again @proceed (result) ->
1335 if result then use result @walk
1336 @step -> done()
1337```
1338
1339Or, even better, use task constructors instead of functions with callbacks:
1340```
1341 @step -> fetch()
1342 @step( r ) -> more_fetch() if r
1343 @step( r ) -> fetch_again() if r
1344 @step( r ) -> use r if r
1345```
1346
1347Extensions
1348----------
1349
1350The l8 API defines a concept of Task/Path/Step entities that works nicely in
1351the async/callback dominated world of Javascript and yet manage to provide some
1352useful tools (hopefully) to address the infamous "callback hell" issue.
1353
1354However these tools are very basic.
1355
1356One way to improve on that situation is to use one of the multiple existing
1357"promise" handling libraries, such as Q, when and rsvp. See also
1358http://howtonode.org/promises and https://gist.github.com/3889970
1359
1360What is also needed are more sophisticated yet simpler to use solutions. There
1361are many styles of coding regarding the orchestration of complex interactions
1362between fragments on code. We are no longer restricted to the signal/kill
1363mechanism from the 70s in Unix!
1364
1365In addition to the classics (semaphores, mutexes, message queues, conditional
1366variables, signals, monitors, events...) newer mechanisms are experimented or
1367rediscovered. Things like channels in Go, actors in Erlang, reactive system in
1368Angular, these things are interesting to explore.
1369
1370I believe l8 may help do that. For sure the granularity is not the same as in
1371native implementations. Instead of dealing with statements or even machine
1372code level instructions, l8 deals with much bigger "steps". However, regarding
1373synchronisation, this difference of scale does not imply a difference of
1374nature. As a consequence, solutions that work at the machine level may prove
1375also productive at the "step" higher level.
1376
1377Hence, proposals for extensions are welcome.
1378
1379Enjoy.
1380
1381 Jean Hugues Robert, aka @jhr, october/november 2012.
1382
1383PS: all this stuff is to relieve me from my "node anxiety".
1384See http://news.ycombinator.com/item?id=2371152
1385