Giter Site home page Giter Site logo

getify / asynquence Goto Github PK

View Code? Open in Web Editor NEW
1.7K 1.7K 149.0 515 KB

Asynchronous flow control (promises, generators, observables, CSP, etc)

JavaScript 99.31% HTML 0.69%
async async-await async-programming csp flow-control generators javascript library observables promises streams

asynquence's People

Contributors

0xflotus avatar ajithbhat avatar getify avatar nolsherry avatar taytay avatar tomjnsn avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

asynquence's Issues

Auto-fulfilled step with message(s)

Just like with promises, pass any value other than function to ASQ, get automatic message passing to next step.

ASQ(3)
.val(function(msg){
   console.log(msg); // 3
});

This is helpful short-hand for:

ASQ()
.val(function(){
   return 3;
})
.val(function(msg){
   console.log(msg); // 3
});

Also, allow multiple values to ASQ() as multiple messages:

ASQ(3,"foo")
.val(function(msg1,msg2){
   console.log(msg); // 3 foo
});

Also, let this behavior happen to .val(..) as well, so this kind of message injection can happen in the middle of a chain:

ASQ(doSomething)
.val(3,"foo")
.then(function(done,msg1,msg2){
   console.log(msg1,msg2); // 3 foo
});

Multiple messages wrapper

Allow multiple messages to be wrapped in a type-identifiable container (not just an array, but array-like), mostly so val(..) handlers can return multiple messages without array overlap.

ASQ()
.val(function(){
   return ASQ.messages(1,2,3);
})
.val(function(msg1,msg2,msg3){
   console.log(msg1,msg2,msg3); // 1 2 3
});

This is short-hand for doing:

ASQ(function(done){
   done(1,2,3);
})
.val(function(msg1,msg2,msg3){
   console.log(msg1,msg2,msg3); // 1 2 3
});

ASQ.messages(..) wrapper would also be useful for throwing multiple error messages:

ASQ(function(done){
   throw ASQ.messages(1,2,3);
})
.or(function(err1,err2,err3){
   console.log(err1,err2,err3); // 1 2 3
});

And ASQ.messages(..) wrapper would automatically be the new type (though it's still array-like) wrapper of message passed in when you have multiple messages sent from a gate segment.

ASQ()
.gate(
   function(done){ done(1,2,3); },
   function(done){ done(4); }
)
.val(function(msgs1,msg2){
   console.log(Array.isArray(msgs1)); // true
   console.log(msgs1.__ASQ__); // true
   console.log(msgs1); // [1,2,3]

   console.log(msgs2); // 4
});

Now errors are reported too aggressively

In contrast to #35, there's lots of patterns where you legitimately know that it's ok for an error to not be forced as a thrown error, because you will eventually pipe that sequence into another one (so it won't be lost).

We need a way to mark a sequence so it defers its error reporting.

Note: This is basically the reverse of what others in the promise world are doing, by adding a done() that lets you mark a promise as not being further chained, and so any latent errors should be thrown. Instead of opting into aggressive error reporting, we'll offer an opt-out.

Can a runner drive a sequence synchronously instead of async?

Right now, if I use runner on a sequence like this:

ASQ()
.runner(ASQ.iterable()
.then((theToken)=> {
var currentTime = Date.now();
return currentTime;
})
.then((lastTime)=>{
var currentTime = Date.now();
console.log("elapsed = "+(currentTime - lastTime));
return currentTime;
})
.then((lastTime)=>{
var currentTime = Date.now();
console.log("elapsed = "+(currentTime - lastTime));
return currentTime;
})
.then((lastTime)=>{
var currentTime = Date.now();
console.log("elapsed = "+(currentTime - lastTime));
return currentTime;
})
.then((lastTime)=>{
var currentTime = Date.now();
console.log("elapsed = "+(currentTime - lastTime));
return currentTime;
}));

elapsed = 5
elapsed = 5
elapsed = 4
elapsed = 5

If I drive the same sequence synchronously (with a for loop), it obviously doesn't pay the penalty for putting the next run on the queue. Is there something fundamental about the architecture of runner that would prevent me from making a runner call the next step synchronously if the next step was ready to run? (In other words, if we weren't waiting on a promise or another async construct).

Best way to open lots of files

I want to use Asynquence to take a list of globbed files and open them all to perform some operation and return a dictionary.

So I start out by globbing the directory to get these markdown files. That's step one.

function blog (req, res, next) {
  ASQ(getBlogPostFileNames)
}


function getBlogPostFileNames (done) {
  var options = { cwd: path.join(__dirname, '../blog') }

  glob('*.markdown', options, done.errfcb)
}

Now I'm stuck.

I could just map the array that I get, and nobody would mind, but I'm trying to think asynchronously. I have some number of operations to do, and I don't care about the order. Seems like the perfect use case for a gate (!)

The only way I can see to proceed is to create a separate sequence, which I have nothing against in principle, but I was wondering if I was missing some bit of the API that enables me to split an array out and turn it into a gate as a next step.

Something like:

ASQ(function multipleValues (done) {
  done([1, 2, 3])
}).each(function (done, val) {
  done(val * val)
}).val(function (/* args */) {
  console.log([].slice.call(arguments, 0))
})

[thread moved]: asynquence error handling and API pros/cons

[Editor: I have moved part of a thread from another repo, started by @benjamingr, here since it deals directly with asynquence]

Consider this:

myService().then(function(){
doSomething(); // may throw an error, e.g. JSON.parse
});

Most promise libraries include a way to debug promise based code without having to > always add a .catch(function(e){ throw e}) handler, some examples:

  • Native promises in Firefox use garbage collection in order to track unhandled rejections > and log the errors to the console.
  • Libraries like Q use .done to signal that a chain is terminated and rejections that got to that point log.
  • Bluebird promises track unhandled rejection approximately (in a way that practically always works).

API extras

Add optional plugins/add-ons/extras/helpers/etc package that provides some additional convenience APIs:

Gate variations:

  • all(..) is an alias of gate(..) (for those who enjoy similarity with Promises)
  • any(..) is like gate(..), except just one segment has to succeed to proceed on the main sequence.
  • first(..) is like any(..), except as soon as any segment succeeds, the main sequence proceeds (ignoring subsequent results from other segments).
  • last(..) is like any(..), except only the latest segment to complete successfully sends message(s) along to the main sequence.
  • none(..) is the inverse of gate(..): the main sequence proceeds only if all the segments fail (with all segment error message(s) transposed as success message(s) and vice versa).

Sequence-step variations:

  • until(..) is like then(..), except it keeps re-trying until success or break() (for loop semantics) before the main sequence proceeds.
  • try(..) is like then(..), except it proceeds as success on the main sequence regardless of success/failure signal. If an error is caught, it's transposed as a special-format success message: { catch: ... }.

This also implies a "plugins" functionality where some internals hooks can be provided (specifically the ability to register something to be injected into each sequence API instance).

for `runner()` plugin, make a test for actual generators

This is a tricky one because generators are invalid syntax and have to be feature-detected using Function or eval, as well as the running of a test for them. Need to figure out how to take a test and get it into a string so the test suite can run the test only in environments where a generator is valid.

Explore how to duplicate a sequence

Given a sequence like:

var sq = ASQ(..).then(..).seq(..).val(..);

Would it be possible to duplicate a sequence, perhaps like:

var sq_template = sq.duplicate();

The duplicated sequence would probably need to start out "paused", perhaps as a mixture of iterable-sequences and normal sequences, where the first step can be externally advanced with a next() or start() or something, and the rest of the sequence would be a normal internally iterable sequence.

One way this might be useful:

// restart the sequence to recover from an error
sq.or(function recover(err){
   // swqp out to saved "backup" template
   sq = sq_template;

   // start up the new sequence
   sq.start(); // or iterable-sequence `next()`

   // now, make another "backup" template
   sq_template = sq.duplicate();

   // setup the new sequence to error recover automatically
   sq.or(recover);
});

We could perhaps even have a helper:

// restart the sequence to recover from an error
sq.or(function recover(err) {
   sq = ASQ.restart(sq_template);

   // now, make another "backup" template
   sq_template = sq.duplicate();

   // setup the new sequence to error recover automatically
   sq.or(recover);
});

React needs disposal of events

In the example using react, I see the following code:

ASQ.react(
   // this listener setup handler will be called only once
   function(proceed){
      // we can call `proceed(..)` (or whatever you want to call the param!)
      // every time our stream/event fires, instead of just once, like
      // normal promise triggers
      $("#button").click(function(evt){
         // fire off a new sequence for each click
         proceed(this.id);
      });
   }
)
// each time our reactive event fires, process the rest of this sequence
.then(..)
.seq(..)
.then(..)
.val(..);

The problem with this code is that there's explicit adding of an event, but at no point is there ever a removal of said event when the sequence comes to an end, which leads to leaking event handlers.

appendable queue

Is it possible to fit asynquence to the use case described below?

  1. There is a queue :)
  2. Few arbitrary events listener(s) catch(es) some events and append(s) events-based (promises of) messages to the queue one by one.
  3. There is a handler attached to the queue processing incoming messages one by one for side effects.

In fact it is close to actor model.

`runner` to run/iterate over iterable-sequences or generators

A contrib plugin called runner that adds runner(..) to the sequence API.

This function (inspired by spawn and others like it) takes either an iterable-sequence or a generator (that must yield sequences or thennable-promises), and runs the iteration to completion (if any).

Examples:

function double(x) {
    if (!x) x = 1;

    return ASQ(function(done){
        setTimeout(function(){
            done(x * 2);
        },500);
    });
}

ASQ(2)
.runner(
    ASQ.iterable()
    .then(double)
    .then(double)
    .then(double)
    .then(double)
)
.val(function(msg){
    console.log("finished value: " + msg);
    // finished value: 32
});

Or:

function double(x) {
    return ASQ(function(done){
        setTimeout(function(){
            done(x * 2);
        },500);
    });
}

ASQ(2)
.runner(function*(x){
    if (!x) x = 1;

    while (x < 32) {
        // yields sequences
        x = yield double(x);
    }

    yield x;
)
.val(function(msg){
    console.log("finished value: " + msg);
});

Or:

function double(x) {
    return new Promise(function(resolve,reject){
        setTimeout(function(){
            resolve(x * 2);
        },500);
    });
}

ASQ(2)
.runner(function*(x){
    if (!x) x = 1;

    while (x < 32) {
        // yields standard promises
        x = yield double(x);
    }

    yield x;
})
.val(function(msg){
    console.log("finished value: " + msg);
});

Contrib: waterfall(..)

Need a waterfall(..) contrib plugin, which takes a list of functions, and applies them in order just like a normal sequence of then(..)s, but it collects aggregated output, where step 2 gets output from step 1, step 3 gets output from both step 2 and 3, step 4 gets messages from 1-3, etc.

The final success message(s) from waterfall(..) is the full aggregate of all success messages. An error anywhere along the way behaves like an error in a normal sequence.

Note: this method is sorta conceptually similar to an array-map, where we're mapping a set of steps to a set of messages.

externally iterable sequences (aka: asynquence "generators")

Normal asynquence sequences are internally iterable, that is, each step of the sequence gets its own completion trigger to advance the sequence iteration to the next step.

However, there's also a need for an asynquence sequence to be externally iterable. That is, have a sequence of steps set up, where the control-flow for advancing the sequence is outside the sequence. For instance, a for-of loop, or other such things, which advance a queue of steps.

The question is, should all sequences be both internally and externally iterable, or should there be two different kinds of sequences, one for each.

IOW:

var steps = ASQ()
.then(function(done){
   // step 1
})
.then(function(done){
  // step 2
});

For that sequence, should you be able to do something like:

steps.next();
steps.next();

The problem with that is if you externally advance a sequence and that step itself has a completion trigger, it's kind of a race to see who goes first, and you could accidentally advance.


By contrast, you could construct separate sequence types, for iterables, like:

var steps = ASQ.iterable()
.then(function(msg){
  // step 1
  // return a message instead of sending it on
})
.then(function(msg){
  // step 2
});

In this way, the iterable sequence is only controllable externally (no done triggers), so each step doesn't get a completion trigger as it normally would.

This would make the sequence basically like a generator (indeed it might end up someday implemented with them).

Instead of step 1 passing a message onto step 2 directly, it looks like:

var ret1 = steps.next("step 1");

steps.next(ret1.value); // pass the output message from step-1 onto step-2

For consistency/compatibility sake, the return value from next() would always be an object of { value: ..., done: true/false } format, just like with normal generators. The value is any return value from the function, and done is if the sequence currently has no more steps registered on it.

Obviously, an iterable sequence would not have all the other API methods/helpers that normal internal sequences do, such as gate(), seq() and val(), as they don't make any sense in this usage context. It would only have then() for steps, and or() for error handling.

Like generators, you could do steps.throw(..) to throw an error at the sequence at that current step. Or, inside a step, throwing an error (intentionally or not) would be caught and propagated into the or() handlers of the sequence.

Need to investigate best approach/design ideas for this. Thoughts welcomed.

A Typescript definitions file would be great

I'll be using asynquence in Typescript in addition to Javascript.

A definition file is a header file. A file like this (asynquence.d.ts) would tell Typescript what the asynquence API looks like, so that it can auto-complete, give compiler errors if you pass the wrong type, leave off a parameter, etc. It will obviously take some familiarity with Typescript to write one. There are definition files for most popular frameworks/libraries: DefinitelyTyped project.

I will start playing with generating one, and will post back here if I have much luck.

make a plugin which vends a native promise off a sequence

For the case where you have an asynquence sequence and want to vend a native promise that's chained to the sequence, create an API method via an optional contrib plugin.

For example:

ASQ()
.then(function(done){
   setTimeout(function(){
      done(2);
   },1000);
})
.toPromise()
.then(function(msg){
   console.log(msg); // 2
});

What happens to aborted sequences?

If you seq() or pipe() sequence "A" into sequence "B", but sequence "A" gets aborted (or is already aborted), should sequence "B" know about it, or just hang forever waiting for a notification it will never get?

For context, if "A" were error'd out, "B" would hear about it through the error stream (or() chain).

But currently, and aborted sequence doesn't make that fact known to anyone, even another sequence that's listening in on him, so "B" just hangs. Dunno if this should be considered desired or bad/buggy.

Hmmm....

var sq1 = ASQ()
.then(function(done){
   // abort 500ms from now!
   setTimeout(done.abort,500);
});

ASQ()
.then(function(done){
   // continue 1000 ms from now!
   setTimeout(done,1000);
})
// now, let's wait on `sq1`, futily
.seq(sq1)
.then(function(done){
   // never gets called because `sq1` silently aborted and this sequence can't tell!
   console.log("This will never happen");
});

One possible solution, partial anyway, is that if you try to seq() or pipe() off an already-aborted sequence, THAT could throw an error into your existing sequence. That would seem helpful.

But what if you are already listening to the sequence when it aborts itself? A sequence can't tell if it is being listened to by another sequence, so it would have no way to know that it ought to throw up an error. And I don't want errors thrown on abort, since abort is designed to stop the sequence in its tracks, dead.

Maybe there needs to be an "abort" channel/stream, in addition to the "success" and "error" streams, so that silently, whenever any part of a sequence or sequence-chain is aborted, the whole thing aborts itself all the way up.

So, should the second ASQ sequence above:

  1. continue normally (listen for, then ignore the abort for sq1)?
  2. abort itself too once sq1 is aborted (either before or after we listen in)?
  3. hang (as it currently does)?

sync vs. async resolution of the sequence steps

Investigate making this pattern not result in nested timeouts (which is then subject to clamping):

https://gist.github.com/getify/5d5e0090cf446248c724#file-gistfile2-js

Idea for how: https://twitter.com/juandopazo/status/365482783122010115

BTW, this would also imply that the chain executes all subsequent but waiting steps in the current "event turn" synchronously, instead of always deferring each next step via timeouts, which further avoids the nested timeouts-clamping thing.

contrib: return ASQ as "public API" of contrib plugin

Since contrib wrapper has no public API of its own, just return ASQ. Could make it easy or canonical to just do:

var ASQ = require("asynquence-contrib");

Instead of:

var ASQ = require("asynquence");
require("asynquence-contrib");

Note: need to verify that _asynquence-contrib_s require("asynquence") is indeed pulling in the same global asynquence, and not its own dependency, which could be causing version mis-match if so.

"Reactive Sequences" (asynquence's "reactive" pattern)

This idea exploration comes from a conversation with @mgonto. Almost certainly relies on #29.

We can already do something like:

$("#button").click(function(evt){
   ASQ(this.id)
   .then(..)
   .seq(..)
   .then(..)
   .val(..)
});

In that way, we create a new sequence to handle each incoming "event" from a "stream", which is sorta the spirit of how Reactive observables work.

The question is, could we make first-class support for something like this pattern:

ASQ.react(
   // this react/listen handler will be called only once
   function(proceed){
      // we can call `proceed(..)` (or whatever you want to call the param!)
      // every time our stream/event fires, instead of just once, like
      // normal promise triggers
      $("#button").click(function(evt){
         // fire off a new sequence for each click
         proceed(this.id);
      });      
   }
)
// each time our reactive event fires, process the rest of this sequence
.then(..)
.seq(..)
.then(..)
.val(..);

`seq( func )` or `promise( func )` isn't re-executing `func` on duplicated sequence

This is kinda a bizarre oops.

Reproduce code:

var count = -1;

var a = ASQ()
.seq(function(){
    return ASQ(function(done){
        count++;
        if (count === 0) done.fail(count);
        else done(count);
    });
})
.val(function(msg){ console.log("success:" + msg); })
.or(function(err){ console.log("err:" + err); });

var b = a.duplicate();
b.unpause();

Should output "err:0" and "success:1", but since the seq( .. ) function callback isn't being re-executed, "err:0" is printed twice. :(

sugar to handle node-style callbacks

[Update]: I finally settled on errfcb, so I updated this description to reflect

Inspired from: https://github.com/then/promise#promisedenodeifyfn and thanks to @julienw's tweet.

Two suggestions to consider (either/both):

  1. The done trigger provided to then(..) callbacks should have a flag on it called errfcb: ASQ().then(function(done){ foo( "bar", done.errfcb ); }) which automatically detects the node-style callback err parameter and calls done.fail(err), otherwise just calls done(..) directly.
  2. More intrusive, but the main sequence API could have errfcb() on it, which returns not a chain continuation of the sequence as normal, but rather a node-style callback that continues the sequence: sq = ASQ()....; foo( "bar", sq.errfcb() )

The first feels like something to just roll directly into asynquence core, and the second feels more like something to put into contrib.

Other thoughts welcome.


So, it's probably gonna look like this:

ASQ()
.gate(
   function(done){
      fs.readFile( "foobar.txt", done.errfcb );
   },
   function(done){
      fs.readFile( "bazbam.txt", done.errfcb );
   }
)
.then(function(done, foobar, bazbam){
   fs.writeFile( "both.txt", foobar + bazbam, done.errfcb );
})
...

-or-

var sq = ASQ();

fs.readFile( "foobar.txt", sq.errfcb() );
fs.readFile( "bazbam.txt", sq.errfcb() );

sq.then(function(done, foobar, bazbam){
   fs.writeFile( "both.txt", foobar + bazbam, done.errfcb );
})
...

`ASQ.isMessageWrapper(..)` should check for actual array

ASQ.isMessageWrapper(..) currently only checks for ASQ branding, which means it's incapable of distinguishing between an ASQ instance and a message wrapper. Should fix the check so that it looks for Array.isArray(..) to match the tested object as well.

research adding a "co" API method/plugin for CSP-style coroutines

Having two+ generators, or iterable-sequences, which are cooperatively concurrent, and message each other as they process through their steps. Imagining something like:

ASQ()
.co(
   function*(){
      var x = yield 2;
      x = yield (x * 2);
      console.log(x); // 8
   },
   function*(){
      var y = yield 6;
      y = yield (y * 4);
      console.log(y); // 12
   }
)
.val(function(){
   console.log("coroutines done");
});

Gate example in README does not work.

I'm trying to use the gate example in the README to learn how to pass messages (debugging #2). However, the messages don;t seem to get passed to the gate segments.

Here is the code. It is almost an exact copy of the example in the README.

var ASQ = require('asynquence');                                                                                                                                                              

ASQ()                                                                                                                                                                                         
// normal async step                                                                                                                                                                          
.then(function(done){                                                                                                                                                                         
    setTimeout(function(){                                                                                                                                                                    
        done("hello");                                                                                                                                                                        
    },1000);                                                                                                                                                                                  
})                                                                                                                                                                                            
// parallel gate step (segments run in parallel)                                                                                                                                              
.gate(                                                                                                                                                                                        
    function(done,msg1){ // gate segment                                                                                                                                                      
        setTimeout(function(){                                                                                                                                                                
            done(msg1,"world");                                                                                                                                                               
        },500);                                                                                                                                                                               
    },                                                                                                                                                                                        
    function(done,msg1){ // gate segment                                                                                                                                                      
        setTimeout(function(){                                                                                                                                                                
            done(msg1,"mikey");                                                                                                                                                               
        },100); // segment finishes first, but message still kept "in order"                                                                                                                  
    }                                                                                                                                                                                         
)                                                                                                                                                                                             
.then(function(_,msg1,msg2){                                                                                                                                                                  
    console.log("Greeting: " + msg1[0] + " " + msg1[1]); // 'Greeting: hello world'                                                                                                           
    console.log("Greeting: " + msg2[0] + " " + msg2[1]); // 'Greeting: hello mikey'                                                                                                           
});          

Output:

Greeting: undefined world
Greeting: undefined mikey

Am I going crazy, or is something wrong here?

Node 0.10.9
ASQ 0.1.0

Improvement: `map(..)` getting `arr` and/or `each` from value-message stream

Per this gist from @EdJ, map(..) should be able to detect if it's missing either arr or each argument or both, and if so, attempt to pull them from the stream of value-messages passed from the previous step.

For instance, this should work:

ASQ()
.then(function(done){
   done([1,2,3]); // provide the `arr` for `map(..)` to iterate over
})
.map(function(item,done){
   done(item+1);
})
.val(function(arr){
   console.log(arr); // [2,4,6]
});

Even if just map() is called on a chain with no args at all, it should attempt to find both the arr and each from the value message stream.

Note: it should still pass any additional stream messages onto the each callback itself, like:

ASQ()
.val(function(){
   return ASQ.messages([1,2,3],4);
})
.map(function(item,done,msg){
   done(item * msg);
})
.val(function(arr){
   console.log(arr); // [8,16,24]
});

provide optional "legacy.js" file

Provide an optional "legacy.js" file to use if loading lib in old/crappy browser. Include the ES5 shims (or anything else) the lib needs to ensure it works properly.


Research if there's a way for the build-processes (for asynquence and asynquence-contrib) to scan/parse the files for such usage and automatically build "legacy.js". Probably needs its own tool to do that well.

Extending a local instance of ASQ instead of the global one

I was wondering if there is a way to extend a local instance of ASQ rather than polluting the global ASQ instance. If I'm right, this global ASQ instance is shared by by every module in your project using ASQ (including third party libraries), so it's possible to get conflicts there (especially when ASQ becomes widely used...).

`map(..)` to async map array items to new values

Per this twitter conversation and inspired by async.map(..), add map(arr, eachFn) to asynquence, which asynchronously maps values from arr into new values by calling eachFn(..) for each value.

The eachFn(..) function is invoked with item and doneTrigger parameters, as well as any previous-step sequence messages (just like with normal gates). doneTrigger(..) should be called with the new value(s) for the respective array item.

The final sequence message to come out of a map(..) should be the newly constructed array of mapped values.

`fork()` off a sequence

Given a sequence sq, sq.fork() would create a new sequence which has as its first step a "listener" to the main sq sequence at whatever point in the chain fork() is called. The new sequence would have its next subsequent step sent any output (success or error) once the main sq step reaches that point.

Since fork() returns a new sequence, you can't call fork().fork()... on the main chain, as the second fork() would be off the new sequence and would thus create a third sequence, etc. However, var a = sq.fork(); var b = sq.fork(); ... would give you the ability to fork the main sequence as many times as desired.

var sq = ASQ(..).then(..).val(..).seq(..)...;

var sq2 = sq.fork().then(..).then(..).gate(..)...;
var sq3 = sq.fork().val(..).seq(..)...;

fork() also needs to work off of iterable-sequences (producing another iterable-sequence), where calling isq.next() at a point where there's one or more forked iterable-sequences listening/waiting would essentially trigger a next() call on not only the main isq iterable-sequence, but all the forked sequences off that fork point as well.

Calling gate with an array of functions

I want to dynamically create an array of functions to be executed in parallel. How can I pass that to gate to execute? I'm not sure I'm doing it correctly. I think I might need to use .seq() here like this:

    .seq(function(endpoints) {   // for this example, endpoints is an array of two object.                                                                                                                                                          
        var calls = [];                                                                                                                                                                   
        endpoints.forEach(function(endpoint) {                                                                                                                                            
            calls.push(function(done) {                                                                                                                                                   
                endpoint.getConfig(function(err, config) {                                                                                                                                
                    if (err) {                                                                                                                                                            
                        console.log(err);                                                                                                                                                 
                    }                                                                                                                                                                     
                    config.id = endpoint.id;                                                                                                                                              
                    console.log(config.id);                                                                                                                                               
                    done(config);                                                                                                                                                         
                });                                                                                                                                                                       
            });                                                                                                                                                                           
        });                                                                                                                                                                               
        var seq = ASQ();                                                                                                                                                                  
        seq.gate.apply(this, calls);                                                                                                                                                      
        return seq;                                                                                                                                                                       
    })                                                                                                                                                                                    
    .then(function(done, configs) {                                                                                                                                                       
        console.log(configs);                                                                                                                                                             
        var configMap = _.pluck(configs, 'id');                                                                                                                                           
        console.log(configMap);                                                                                                                                                           
        done();                                                                                                                                                                           
    }); 

I believe that configs in that final .then() should be an array, but it is just a single object. Am I not using .gate() and .seq() right here?

should contrib's `wrap(..)` pass-thru `this` binding?

Right now, wrap(..) forces the this binding of its passed fn function to be ΓΈ (an empty DMZ object). So, if you need to wrap a this-aware function (aka "method"), you have to call .bind(..) yourself at time of wrapping:

var something = ASQ.wrap( obj.methodName.bind(obj) );

something(..);

If you forget, you lose your this binding, and things go bummer. Would it be less of a footgun if we passed through the this binding?

var something = ASQ.wrap( obj.methodName );
var something2 = ASQ.wrap( obj.methodName );

something(..); // fails
something.call(obj, ..); // works

something2 = something2.bind(obj);
something2(..); // works

obj.something = something;
obj.something(..); // works

Not sure if this feels better or more footgun'ish. It does preserve the flexibility to have an asynquence-wrapped function that can still be bound to another this later if needed.


I don't really want to complicate the API with another utility that handles this binding, but we could do this I guess:

var something = ASQ.wrap( obj.something, { this: obj } );

something(..); // works

Even something else we could do is make that binding be a "soft binding", where it defaults, but lets you override:

var something = ASQ.wrap( obj.something, { this: obj } );

something(..); // works against `obj`
something.call( obj2, .. ); // also works, but against `obj2`

That feels the most flexible and useful. But is it? Thoughts?

API naming

asynquence looks interesting has a lot of potential I think. The API looks very clean and concise. There are a few minor things that hurt my eye though:

  • The abbreviation "ASQ" doesn't look very friendly and accessible to me and for some reason wards me off. This may be personal though. How about just the character "A"?
  • The function name for adding an error handler to the sequence is or. I find this very confusing. When I see or in the context of sequences/flows, I think about "doing this or that", "doing any of the two". The name or has really nothing to do with errors. I would rename this to catch, which everybody will understand immediately.
  • The method gate has an alias all. I personally find the name all way more intuitive. There is nothing wrong with reusing familiar dictionary from Promises if you ask me :). Anyhow, please choose one of the two and remove the alias. I don't see a reason to have two functions doing the same thing here, and it is important to keep the API as concise and consistent as possible. Aliases can easily lead to confusion, you will end up seeing both used mixed in code and on the web, people may wonder if there is a difference between the two and get confused - totally unnecessary.

Just my 2 cents to make the API even better...

make iterable-sequences **actually** iterable (per ES6 for-of)

The goal is to be able to do:

var isq = ASQ.iterable()
.then(function(){ return 5; })
.then(function(){ return 10; })
.then(function(){ return 15; });

for (var v of isq) {
   console.log(v);
}
// 5   10   15

This example code makes a custom iterator for an object. It works in FF nightly. Need to research how to properly feature-detect this stuff and include something like it in the iterable-sequence contrib plugin so it returns itself when asked for its @@iterator. Probably needs a future-proof Symbol.iterator feature detect in there too.

const iterator = (function() {
  try {
    for (var _ of Proxy.create({get: function(_, name) { throw name; } }))
      break;
  } catch (name) {
    return name;
  }
  throw 'wat';
})();

var mine = {};

// construct a fake iterator that just spits out the series of integers from 1-5
mine[iterator] = function() { return {next:function(){
    mine.count = mine.count || 0;
    mine.count++;
    var ret = { done: false, value: mine.count };
    if (mine.count > 5) ret.done = true;
    return ret;
}}};

for (var x of mine) {
    console.log(x);
}
// 1  2  3  4  5

References:

runner with multiple generators: make it more CSP-like

Right now, the runner(..) plugin uses a dummy-simple round-robbin scheduling to "cooperate" between multiple generators. It also uses a dummy-simple messaging mechanism to pass along one yielded value to the next generator and back, etc.

A more sophisticated mechanism is called for. One more like (though not necessarily identical to) the one detailed here: http://swannodette.github.io/2013/08/24/es6-generators-and-csp/

I'm envisioning something like:

// just a silly number generator that's promise-async
function getVal() {
   return new Promise( function(resolve,reject){
      setTimeout( function(){
         if (!getVal.__number) getVal.__number = 0;
         resolve( getVal.__number += 10 );
      }, 100 );
   } );
}

ASQ( 2, 3 )
.runner(
   // co-rountine 1
   function*(token){
      token.messages; // [2, 3]

      token.messages[0] += yield getVal(); // pauses while promise resolves
      token.messages[1] += yield getVal(); // pauses while promise resolves
      token.messages.push( token.messages[0] + token.messages[1] );

      token.messages; // [12, 23, 35]

      yield token; // hand control over to next co-rountine

      token.messages; // [ 100 ]

      token.messages[0] += yield getVal(); // pauses while promise resolves

      // implicit finish means transfer control to next co-rountine, if any
   },
   // co-rountine 2
   function*(token){
      token.messages; // [12, 23, 35]

      token.messages = [
         token.messages[0] + token.messages[1] + token.messages[2]
      ];
      token.messages; // [ 70 ]

      token.messages[0] += yield getVal(); // pauses while promise resolves
      token.messages; // [ 100 ]

      yield token; // hand control over to next co-rountine

      token.messages; // [ 140 ]

      token.messages.push( "Hello World" );

      // no magic: explicitly yield `token.messages`, or any other direct value
      // if desired. if you don't explicitly yield something non-undefined at the
      // end, the last non-`undefined`, non-`token` value yielded will be the
      // outbound message(s) from this run
      yield token.messages;
   }
)
.val( function(msgs){
   console.log( msg[0], msgs[1] ); // 140 "Hello World"
} );

OK, so as I wrote that out, I kinda like it even more.

Basically each co-rountine (aka generator) is given a token with a messages "channel" (array). You can add/remove from the channel as you see fit. You can even yield your current generator with a promise and have it restarted as many times as you see fit. If however you actually yield token (or something that results in it), then you are explicitly transferring control to the next co-rountine. Ordering of control is still simple round-robbin.

Thoughts?

Would be especially interested to hear any reactions/criticisms from @swannodette. :)

Automatically detect and consume another asynquence

Consider:

var sq1 = ASQ(..)..;

ASQ()
.then(..)
.seq(function(){
   return sq1;
})
..

The seq(function(){ return sq1; }) syntax is needlessly verbose, and should have a short-hand, perhaps like:

var sq1 = ASQ(..)..;

ASQ()
.then(..)
.seq(sq1)
..

Since seq(..) expects a function, if it instead gets an object, we could probably safely assume that the object is an asynquence object, and just consume it (pipe its output and error streams) automatically, instead of requiring the function execution indirection.


Only issue to resolve: seq(..) would detect an object and assume an asynquence, and Issue #5 suggests that both ASQ(..) and val(..) will assume a non-function parameter as an auto-fulfilled step message.

Should ASQ(..) detect an asynquence as a special case (using branding or duck-typing)? Should:

var sq1 = ASQ(..)..;

ASQ(sq1)
.then(..)
..

... be legal? In that specific case, you could always do this, instead:

var sq1 = ASQ(..)..;

sq1
.then(..)
..

So, maybe ASQ(..) doesn't need to special-case detect. Hmmm.....

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.