getify / asynquence Goto Github PK
View Code? Open in Web Editor NEWAsynchronous flow control (promises, generators, observables, CSP, etc)
Asynchronous flow control (promises, generators, observables, CSP, etc)
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. :(
Provide promise(..)
which accepts one or more promises and consumes them into the sequence.
Some other internals that would be helpful for plugins to have read-only access to:
createSequence()
brandIt()
[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):
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.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 );
})
...
Is it possible to fit asynquence to the use case described below?
In fact it is close to actor model.
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:
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.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...
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. :)
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
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.
http://zef.me/6096/callback-free-harmonious-node-js
A thunk is a partially-applied (aka curried) function, with only the callback missing.
You can yield
out a thunk and the callback will be supplied by the runner, and when it's called, the generator will resume. Same concept as promises and sequences.
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...).
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");
});
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.
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.
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:
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
});
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.
this might break some things. but i think it's the right way for it to behave, instead of accidentally swallowing many errors. will revisit if too much is broken.
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.
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 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.
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).
Iterable-sequences can (and should) have a pipe(..)
that works exactly like it does for normal sequences.
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(..);
[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).
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?
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.....
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:
sq1
)?sq1
is aborted (either before or after we listen in)?Per a suggestion by @josdejong in #42.
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))
})
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
});
seq(..)
on a normal sequence needs to be able to listen to an iterable-sequence and proceed as expected (success or error) when the iterable-sequence reaches that step.
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.
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);
});
In addition, bundle.js needs to optionally include a test only if building with the plugin in question.
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, throw
ing 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.
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.
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);
});
See this section of the runner(..)
plugin code:
https://github.com/getify/asynquence/blob/master/contrib/plugin.runner.js#L57-L75
The check there incorrectly only looks ASQ.isSequence(..)
and doesn't account for having an ASQ message-wrapper yielded/returned. Should also check for ASQ.isMessageWrapper()
on the value.
Relies on #26 being fixed.
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 throw
ing 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
});
bluebird has a method(..)
utility that wraps a normal callback-accepting function into a promise-returning function. maybe we need that?
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).
Let's type-detect the params passed to the map(..)
plugin, so it can accept either the current map(arr,fn)
(async) or map(fn,arr)
.
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.
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?
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.
Make 'contrib' be its own separate npm package called "asynquence-contrib" for easier usage in node.
Bug: when new or(..)
handlers are registered, if the iterable-sequence is already in the error state, the handlers aren't notified. They should be.
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.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.