Skip to content
This repository has been archived by the owner on Dec 7, 2020. It is now read-only.

Commit

Permalink
adding 'duplicate()' to both core and contrib:iterable, closes #29
Browse files Browse the repository at this point in the history
  • Loading branch information
getify committed Apr 3, 2014
1 parent 455346d commit 8e69402
Show file tree
Hide file tree
Showing 9 changed files with 270 additions and 19 deletions.
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# asynquence

A lightweight (**~1.8k** minzipped) micro-lib for asynchronous flow-control using sequences and gates.
A lightweight (**~1.9k** minzipped) micro-lib for asynchronous flow-control using sequences and gates.

## Explanation

Expand Down Expand Up @@ -70,6 +70,14 @@ There are a few convenience methods on the API, as well:

`sq.fork()` is (sort-of) sugar short-hand for `ASQ().seq(sq)`.

* `duplicate()` creates a separate copy of the current sequence (as it is at that moment). The duplicated sequence is "paused", meaning it won't automatically run, even if the original sequence is already running.

To unpause the paused sequence-copy, call `unpause()` on it. The other option is to call the helper `ASQ.unpause(..)` and pass in a sequence. If the sequence is paused, it will be unpaused (and if not, just passes through safely).

**Note:** Technically, `unpause()` schedules the sequence to be unpaused as the next "tick", so it doesn't really unpause *immediately* (synchronously). This is consistent with all other calls to the API (`ASQ()`, `then()`, `gate()`, etc), which all schedule procession of the sequence on the next "tick".

`unpause()` is only present on a sequence API in this initial paused state after it was duplicated from another sequence. It is removed as soon as that next "tick" actually unpauses the sequence. It is safe to call multiple times until that next "tick", though that's not recommended. The `ASQ.unpause(..)` helper is always present, and it first checks for an `unpause()` on the specified sequence instance before calling it, so that's safer.

* `errfcb` is a flag on the triggers that are passed into `then(..)` steps and `gate(..)` segments. If you're using methods which expect an "error-first" style (aka, "node-style") callback, `{trigger}.errfcb` provides a properly formatted callback for the occasion.

If the "error-first" callback is then invoked with the first ("error") parameter set, the main sequence is flagged for error as usual. Otherwise, the main sequence proceeds as success. Messages sent to the callback are passed through to the main sequence as success/error as expected.
Expand All @@ -84,6 +92,8 @@ If you want to test if any arbitrary object is an *asynquence* sequence instance

`ASQ.iterable(..)` is added by the `iterable-sequence` contrib plugin. See [Iterable Sequences](#iterable-sequences) below for more information.

`ASQ.unpause(..)` is a helper for dealing with "paused" (aka, *just* duplicated) sequences (see `duplicate()` above).

`ASQ.noConflict()` rolls back the global `ASQ` identifier and returns the current API instance to you. This can be used to keep your global namespace clean, or it can be used to have multiple simultaneous libraries (including separate versions/copies of *asynquence*!) in the same program without conflicts over the `ASQ` global identifier.

### Plugin Extensions
Expand Down Expand Up @@ -162,6 +172,8 @@ for (var i=0, ret;

This example shows sync iteration with a `for` loop, but of course, `next(..)` can be called in various [async fashions to iterate](https://gist.github.com/getify/8211148#file-ex2-async-iteration-js) the sequence over time.

Just like regular sequences, iterable sequences have a `duplicate()` method (see ASQ's instance API above) which makes a copy of the sequence *at that moment*. However, iterable sequences are already "paused" at each step anyway, so unlike regular sequences, there's no `unpause()` (nor is there any reason to use the `ASQ.unpause(..)` helper!), because it's unnecessary. You just call `next()` on an iterable sequence (even if it's a copy of another) when you want to advance it one step.

### Multiple parameters
API methods take one or more functions as their parameters. `gate(..)` treats multiple functions as segments in the same gate. The other API methods (`then(..)`, `or(..)`, `pipe(..)`, `seq(..)`, and `val(..)`) treat multiple parameters as just separate subsequent steps in the respective sequence. These methods don't accept arrays of functions (that you might build up programatically), but since they take multiple parameters, you can use `.apply(..)` to spread those out.

Expand Down
47 changes: 39 additions & 8 deletions asq.src.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*! asynquence
v0.3.3-b (c) Kyle Simpson
v0.3.4-d (c) Kyle Simpson
MIT License: http://getify.mit-license.org
*/

Expand All @@ -10,12 +10,6 @@
})("ASQ",this,function DEF(name,context){
"use strict";

var public_api, extensions = {},
old_public_api = (context || {})[name],
ARRAY_SLICE = Array.prototype.slice,
brand = "__ASQ__", ø = Object.create(null)
;

function schedule(fn) {
return (typeof setImmediate !== "undefined") ?
setImmediate(fn) : setTimeout(fn,0)
Expand Down Expand Up @@ -47,6 +41,8 @@
var fn, args;

seq_tick = null;
// remove the temporary `unpause()` hook, if any
delete sequence_api.unpause;

if (seq_aborted) {
resetSequence();
Expand Down Expand Up @@ -541,6 +537,19 @@
return sequence_api;
}

function duplicate() {
var sq;

template = {
then_queue: then_queue.slice(0),
or_queue: or_queue.slice(0)
};
sq = createSequence();
template = null;

return sq;
}

function internals(name,value) {
var set = (arguments.length > 1);
switch (name) {
Expand Down Expand Up @@ -597,13 +606,24 @@
val: val,
promise: promise,
fork: fork,
abort: abort
abort: abort,
duplicate: duplicate
})
;

// include extensions, if any
includeExtensions();

// templating the sequence setup?
if (template) {
then_queue = template.then_queue.slice(0);
or_queue = template.or_queue.slice(0);

// templating a sequence starts it out paused
// add temporary `unpause()` API hook
sequence_api.unpause = scheduleSequenceTick;
}

// treat ASQ() constructor parameters as having been
// passed to `then()`
sequence_api.then.apply(ø,
Expand Down Expand Up @@ -701,6 +721,12 @@
}


var public_api, extensions = {}, template,
old_public_api = (context || {})[name],
ARRAY_SLICE = Array.prototype.slice,
brand = "__ASQ__", ø = Object.create(null)
;

// ***********************************************
// Setup the ASQ public API
// ***********************************************
Expand Down Expand Up @@ -732,6 +758,11 @@
return checkBranding(val) && Array.isArray(val);
};

public_api.unpause = function __unpause__(sq) {
if (sq.unpause) sq.unpause();
return sq;
};

public_api.noConflict = function __noconflict__() {
if (context) {
context[name] = old_public_api;
Expand Down
2 changes: 1 addition & 1 deletion contrib/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# asynquence Contrib

Optional plugin helpers are provided in `/contrib/*`. The full bundle of plugins (`contrib.js`) is **~1.7k** minzipped.
Optional plugin helpers are provided in `/contrib/*`. The full bundle of plugins (`contrib.js`) is **~1.8k** minzipped.

Gate variations:

Expand Down
2 changes: 1 addition & 1 deletion contrib/contrib-wrapper.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*! asynquence-contrib
v0.1.6-b (c) Kyle Simpson
v0.1.7-b (c) Kyle Simpson
MIT License: http://getify.mit-license.org
*/

Expand Down
2 changes: 1 addition & 1 deletion contrib/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "asynquence-contrib",
"version": "0.1.6-b",
"version": "0.1.7-b",
"description": "contrib plugins for asynquence",
"main": "./contrib.js",
"scripts": {
Expand Down
27 changes: 26 additions & 1 deletion contrib/plugin.iterable.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
// "ASQ.iterable()"
(function(){
var template;

ASQ.iterable = function __iterable__() {
var sequence_api,
ARRAY_SLICE = Array.prototype.slice,
Expand Down Expand Up @@ -140,6 +143,19 @@ ASQ.iterable = function __iterable__() {
sequence_errors.length = 0;
}

function duplicate() {
var isq;

template = {
val_queue: val_queue.slice(0),
or_queue: or_queue.slice(0)
};
isq = ASQ.iterable();
template = null;

return isq;
}


// ***********************************************
// Object branding utilities
Expand All @@ -164,12 +180,21 @@ ASQ.iterable = function __iterable__() {
pipe: pipe,
next: next,
"throw": throwErr,
abort: abort
abort: abort,
duplicate: duplicate
});

// templating the iterable-sequence setup?
if (template) {
val_queue = template.val_queue.slice(0);
or_queue = template.or_queue.slice(0);
}

// treat ASQ.iterable() constructor parameters as having been
// passed to `val()`
sequence_api.val.apply(ø,arguments);

return sequence_api;
};

})();
108 changes: 104 additions & 4 deletions contrib/tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -750,7 +750,107 @@
},1000);
});
tests.push(function(testDone){
var label = "Contrib Test #11", timeout;
var label = "Contrib Test #11", timeout,
isq1, isq2, res1, res2
;

function step(num) {
return num * 2;
}

function iterate(isq,seed) {
return ASQ(function(done){
(function doIteration(seed){
var ret;

try {
ret = isq.next(seed);
}
catch (err) {
done.fail(err);
return;
}

if (!ret.done) {
setTimeout(function(){
doIteration(ret.value);
},10);
}
else {
done(ret.value);
}
})(seed);
});
}

// set up an iterable-sequence
isq1 = ASQ.iterable();

isq1
.then(step)
.then(step)
.then(step);

isq2 = isq1.duplicate();

isq1
.then(step)
.or(function(){
console.error("isq1 error");
clearTimeout(timeout);
var args = ARRAY_SLICE.call(arguments);
args.unshift(testDone,label);
FAIL.apply(FAIL,args);
});

isq2
.then(step)
.then(step)
.or(function(){
console.error("isq2 error");
clearTimeout(timeout);
var args = ARRAY_SLICE.call(arguments);
args.unshift(testDone,label);
FAIL.apply(FAIL,args);
});

ASQ()
.gate(
function(done){
iterate(isq1,2).pipe(done);
},
function(done){
iterate(isq2,3).pipe(done);
}
)
.val(function(msg1,msg2){
clearTimeout(timeout);

if (
msg1 === 32 &&
msg2 === 96
) {
PASS(testDone,label);
}
else {
var args = ARRAY_SLICE.call(arguments);
args.unshift(testDone,label);
FAIL.apply(FAIL,args);
}
})
.or(function(){
clearTimeout(timeout);
var args = ARRAY_SLICE.call(arguments);
args.unshift(testDone,label);
FAIL.apply(FAIL,args);
});

timeout = setTimeout(function(){
FAIL(testDone,label + " (from timeout)");
},1000);
});
tests.push(function(testDone){
var label = "Contrib Test #12", timeout;

function step(done) {
var args = ARRAY_SLICE.call(arguments,1);
Expand Down Expand Up @@ -847,7 +947,7 @@
},1000);
});
tests.push(function(testDone){
var label = "Contrib Test #12", timeout;
var label = "Contrib Test #13", timeout;

function Ef(err,msg,delay,cb) {
setTimeout(function(){
Expand Down Expand Up @@ -896,7 +996,7 @@
},1000);
});
tests.push(function(testDone){
var label = "Contrib Test #13", timeout;
var label = "Contrib Test #14", timeout;

ASQ("*","@")
.map(["Hello","World","!"],function(item,done,pre,post){
Expand Down Expand Up @@ -1006,7 +1106,7 @@
},1000);
});
tests.push(function(testDone){
var label = "Contrib Test #14", timeout,
var label = "Contrib Test #15", timeout,
isq, Q = tests.Q
;

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "asynquence",
"version": "0.3.3-b",
"version": "0.3.4-d",
"description": "asynquence: async sequences & gates for flow-control",
"main": "./asq.js",
"scripts": {
Expand Down

0 comments on commit 8e69402

Please sign in to comment.