asynquence
Advanced tools
Comparing version 0.2.0-c to 0.3.0-a
/*! asynquence | ||
v0.2.0-c (c) Kyle Simpson | ||
v0.3.0-a (c) Kyle Simpson | ||
MIT License: http://getify.mit-license.org | ||
*/ | ||
!function(n,e,t){"undefined"!=typeof module&&module.exports?module.exports=t():"function"==typeof define&&define.amd?define(t):e[n]=t(n,e)}("ASQ",this,function(n,e){"use strict";function t(n){return"undefined"!=typeof setImmediate?setImmediate(n):setTimeout(n,0)}function r(){function n(){clearTimeout(E),E=null,S.length=0,T.length=0,I.length=0,Q.length=0}function e(){return x?r():(E||(E=t(r)),void 0)}function r(){var t,r;if(E=null,x)n();else if(j)for(;T.length;){t=T.shift();try{t.apply(m,Q)}catch(u){a(u)?Q=Q.concat(u):(Q.push(u),u.stack&&Q.push(u.stack)),0===T.length&&console.error.apply(console,Q)}}else if(O&&S.length>0){O=!1,t=S.shift(),r=I.slice(),I.length=0,r.unshift(l());try{t.apply(m,r)}catch(u){a(u)?Q=Q.concat(u):Q.push(u),j=!0,e()}}}function l(){function n(){j||x||O||(O=!0,I.push.apply(I,arguments),Q.length=0,e())}return n.fail=function(){j||x||O||(j=!0,I.length=0,Q.push.apply(Q,arguments),e())},n.abort=function(){j||x||(O=!1,x=!0,I.length=0,Q.length=0,e())},n}function p(n,e,r){function u(){clearTimeout(y),y=_=q=h=null}function l(){return v?c():(y||(y=t(c)),void 0)}function c(){if(!(j||x||b)){var e=[];y=null,d?(n.fail.apply(m,h),u()):v?(n.abort(),u()):i()&&(b=!0,_.forEach(function(n,t){e.push(q["s"+t])}),n.apply(m,e),u())}}function i(){if(!(j||x||d||v||b||0===_.length)){var n=!0;return _.some(function(e){return null===e?(n=!1,!0):void 0}),n}}function o(){function n(){if(!(j||x||d||v||b||_[e])){var n=s.messages.apply(m,arguments);q["s"+e]=n.length>1?n:n[0],_[e]=!0,l()}}var e=_.length;return n.fail=function(){j||x||d||v||b||_[e]||(d=!0,h=g.call(arguments),l())},n.abort=function(){j||x||d||v||b||(v=!0,c())},_[e]=null,n}var f,p,h,y,d=!1,v=!1,b=!1,_=[],q={};e.some(function(n){if(d||v)return!0;f=r.slice(),f.unshift(o());try{n.apply(m,f)}catch(e){return p=e,d=!0,!0}}),p&&(a(p)?n.fail.apply(m,p):n.fail(p))}function h(){return j||x||0===arguments.length?w:(S.push.apply(S,o(arguments,i)),e(),w)}function y(){return x||0===arguments.length?w:(T.push.apply(T,arguments),e(),w)}function d(){if(j||x||0===arguments.length)return w;var n=g.call(arguments);return h(function(e){var t=g.call(arguments,1);p(e,n,t)}),w}function v(){return x||0===arguments.length?w:(g.call(arguments).forEach(function(n){h(function(e){n.apply(m,g.call(arguments,1)),e()}).or(n.fail)}),w)}function b(){return j||x||0===arguments.length?w:(g.call(arguments).forEach(function(n){h(function(e){a(n)||(n=n.apply(m,g.call(arguments,1))),n.pipe(e)})}),w)}function _(){return j||x||0===arguments.length?w:(g.call(o(arguments,c)).forEach(function(n){h(function(e){var t=n.apply(m,g.call(arguments,1));a(t)||(t=s.messages(t)),e.apply(m,t)})}),w)}function q(){return j?w:(x=!0,r(),w)}function k(n,e){var t=arguments.length>1;switch(n){case"seq_error":if(!t)return j;j=e;break;case"seq_aborted":if(!t)return x;x=e;break;case"then_ready":if(!t)return O;O=e;break;case"then_queue":return S;case"or_queue":return T;case"sequence_messages":return I;case"sequence_errors":return Q}}function A(){Object.keys(f).forEach(function(n){w[n]=f[n](w,k)})}var E,j=!1,x=!1,O=!0,S=[],T=[],I=[],Q=[],w=u({then:h,or:y,gate:d,pipe:v,seq:b,val:_,abort:q});return A(),w.then.apply(m,o(arguments,i)),w}function u(n){return Object.defineProperty(n,h,{enumerable:!1,value:!0}),n}function a(n){return null!=n&&"object"==typeof n&&n[h]}function l(n,e){return g.call(e).slice(1,n+1)}function c(n){return s.messages.apply(m,l(n,arguments))}function i(n){arguments[n+1].apply(m,l(n,arguments))}function o(n,e){var t,r;for(n=g.call(n),t=0;t<n.length;t++)if(Array.isArray(n[t])&&a(n[t]))n[t]=e.bind.apply(e,[null,n[t].length].concat(n[t]));else if("function"!=typeof n[t]){for(r=t+1;r<n.length&&("function"!=typeof n[r]&&!a(n[r]));r++);n.splice(t,r-t,e.bind.apply(e,[null,r-t].concat(n.slice(t,r))))}return n}var s,f={},p=(e||{})[n],g=Array.prototype.slice,h="__ASQ__",m=Object.create(null);return s=r,s.extend=function(n,e){return~["then","or","gate","pipe","seq","val","abort"].indexOf(n)||(f[n]=e),s},s.messages=function(){var n=g.call(arguments);return u(n),n},s.isMessageWrapper=s.isSequence=a,s.noConflict=function(){return e&&(e[n]=p),s},s}); | ||
!function(n,e,t){"undefined"!=typeof module&&module.exports?module.exports=t():"function"==typeof define&&define.amd?define(t):e[n]=t(n,e)}("ASQ",this,function(n,e){"use strict";function t(n){return"undefined"!=typeof setImmediate?setImmediate(n):setTimeout(n,0)}function r(){function n(){clearTimeout(j),j=null,T.length=0,I.length=0,Q.length=0,w.length=0}function e(){return O?r():(j||(j=t(r)),void 0)}function r(){var t,r;if(j=null,O)n();else if(x)for(;I.length;){t=I.shift();try{t.apply(m,w)}catch(u){a(u)?w=w.concat(u):(w.push(u),u.stack&&w.push(u.stack)),0===I.length&&console.error.apply(console,w)}}else if(S&&T.length>0){S=!1,t=T.shift(),r=Q.slice(),Q.length=0,r.unshift(l());try{t.apply(m,r)}catch(u){a(u)?w=w.concat(u):w.push(u),x=!0,e()}}}function l(){function n(){x||O||S||(S=!0,Q.push.apply(Q,arguments),w.length=0,e())}return n.fail=function(){x||O||S||(x=!0,Q.length=0,w.push.apply(w,arguments),e())},n.abort=function(){x||O||(S=!1,O=!0,Q.length=0,w.length=0,e())},n}function p(n,e,r){function u(){clearTimeout(y),y=_=q=h=null}function l(){return v?c():(y||(y=t(c)),void 0)}function c(){if(!(x||O||b)){var e=[];y=null,d?(n.fail.apply(m,h),u()):v?(n.abort(),u()):i()&&(b=!0,_.forEach(function(n,t){e.push(q["s"+t])}),n.apply(m,e),u())}}function i(){if(!(x||O||d||v||b||0===_.length)){var n=!0;return _.some(function(e){return null===e?(n=!1,!0):void 0}),n}}function o(){function n(){if(!(x||O||d||v||b||_[e])){var n=f.messages.apply(m,arguments);q["s"+e]=n.length>1?n:n[0],_[e]=!0,l()}}var e=_.length;return n.fail=function(){x||O||d||v||b||_[e]||(d=!0,h=g.call(arguments),l())},n.abort=function(){x||O||d||v||b||(v=!0,c())},_[e]=null,n}var s,p,h,y,d=!1,v=!1,b=!1,_=[],q={};e.some(function(n){if(d||v)return!0;s=r.slice(),s.unshift(o());try{n.apply(m,s)}catch(e){return p=e,d=!0,!0}}),p&&(a(p)?n.fail.apply(m,p):n.fail(p))}function h(){return x||O||0===arguments.length?C:(T.push.apply(T,o(arguments,i)),e(),C)}function y(){return O||0===arguments.length?C:(I.push.apply(I,arguments),e(),C)}function d(){if(x||O||0===arguments.length)return C;var n=g.call(arguments);return h(function(e){var t=g.call(arguments,1);p(e,n,t)}),C}function v(){return O||0===arguments.length?C:(g.call(arguments).forEach(function(n){h(function(e){n.apply(m,g.call(arguments,1)),e()}).or(n.fail)}),C)}function b(){return x||O||0===arguments.length?C:(g.call(arguments).forEach(function(n){h(function(e){a(n)||(n=n.apply(m,g.call(arguments,1))),n.pipe(e)})}),C)}function _(){return x||O||0===arguments.length?C:(g.call(o(arguments,c)).forEach(function(n){h(function(e){var t=n.apply(m,g.call(arguments,1));a(t)||(t=f.messages(t)),e.apply(m,t)})}),C)}function q(){return x||O||0===arguments.length?C:(g.call(arguments).forEach(function(n){h(function(e){"function"!=typeof n||"then"in n||(n=n.apply(m,g.call(arguments,1))),n.then(e,e.fail)})}),C)}function k(){return x?C:(O=!0,r(),C)}function E(n,e){var t=arguments.length>1;switch(n){case"seq_error":if(!t)return x;x=e;break;case"seq_aborted":if(!t)return O;O=e;break;case"then_ready":if(!t)return S;S=e;break;case"then_queue":return T;case"or_queue":return I;case"sequence_messages":return Q;case"sequence_errors":return w}}function A(){Object.keys(s).forEach(function(n){C[n]=s[n](C,E)})}var j,x=!1,O=!1,S=!0,T=[],I=[],Q=[],w=[],C=u({then:h,or:y,gate:d,pipe:v,seq:b,val:_,promise:q,abort:k});return A(),C.then.apply(m,o(arguments,i)),C}function u(n){return Object.defineProperty(n,h,{enumerable:!1,value:!0}),n}function a(n){return null!=n&&"object"==typeof n&&n[h]}function l(n,e){return g.call(e).slice(1,n+1)}function c(n){return f.messages.apply(m,l(n,arguments))}function i(n){arguments[n+1].apply(m,l(n,arguments))}function o(n,e){var t,r;for(n=g.call(n),t=0;t<n.length;t++)if(Array.isArray(n[t])&&a(n[t]))n[t]=e.bind.apply(e,[null,n[t].length].concat(n[t]));else if("function"!=typeof n[t]){for(r=t+1;r<n.length&&("function"!=typeof n[r]&&!a(n[r]));r++);n.splice(t,r-t,e.bind.apply(e,[null,r-t].concat(n.slice(t,r))))}return n}var f,s={},p=(e||{})[n],g=Array.prototype.slice,h="__ASQ__",m=Object.create(null);return f=r,f.extend=function(n,e){return~["then","or","gate","pipe","seq","val","abort"].indexOf(n)||(s[n]=e),f},f.messages=function(){var n=g.call(arguments);return u(n),n},f.isMessageWrapper=f.isSequence=a,f.noConflict=function(){return e&&(e[n]=p),f},f}); |
/*! asynquence | ||
v0.2.0-c (c) Kyle Simpson | ||
v0.3.0-a (c) Kyle Simpson | ||
MIT License: http://getify.mit-license.org | ||
@@ -413,2 +413,24 @@ */ | ||
function promise() { | ||
if (seq_error || seq_aborted || arguments.length === 0) { | ||
return sequence_api; | ||
} | ||
ARRAY_SLICE.call(arguments) | ||
.forEach(function __foreach__(pr){ | ||
then(function __then__(done){ | ||
// check if this argument is a non-thennable function, and | ||
// if so, assume we shold invoke it to return a promise | ||
// NOTE: `then` duck-typing of promises is stupid. | ||
if (typeof pr === "function" && !("then" in pr)) { | ||
pr = pr.apply(ø,ARRAY_SLICE.call(arguments,1)); | ||
} | ||
// now, hook up the promise to the sequence | ||
pr.then(done,done.fail); | ||
}); | ||
}); | ||
return sequence_api; | ||
} | ||
function abort() { | ||
@@ -480,2 +502,3 @@ if (seq_error) { | ||
val: val, | ||
promise: promise, | ||
abort: abort | ||
@@ -482,0 +505,0 @@ }) |
@@ -10,5 +10,47 @@ #!/usr/bin/env node | ||
var ASQ = require("./asq.src.js"); | ||
var Q = require("q"); | ||
var tests = require("./tests.js")(doneLogMsg); | ||
var contrib_tests = require("./contrib/tests.js")(doneLogMsg); | ||
tests.Q = Q; // inject Q so the tests can use it | ||
var path = require("path"); | ||
var fs = require("fs"); | ||
var child_process = require("child_process"); | ||
var contrib_tests_file = path.join(__dirname,"contrib","node-tests.js"); | ||
var contrib_tests = [function(done){ | ||
console.log("*** CONTRIB TESTS SKIPPED ***"); | ||
done(); | ||
}]; | ||
// if `./contrib/` exists, run the contrib test suite | ||
if (fs.existsSync(contrib_tests_file)) { | ||
contrib_tests = [function __contrib_tests__(done) { | ||
console.log(""); | ||
var cp = child_process.spawn( | ||
contrib_tests_file, | ||
/*args=*/[], | ||
{ | ||
cwd: path.join(__dirname,"contrib"), | ||
env: process.env, | ||
encoding: "utf8" | ||
} | ||
); | ||
cp.stdout.on("data",function(data){ | ||
console.log(data.toString().replace(/\n$/,"")); | ||
}); | ||
cp.stderr.on("data",function(data){ | ||
console.error(data.toString().replace(/\n$/,"")); | ||
}); | ||
cp.on("close",function(code){ | ||
if (code === 0) { | ||
done(); | ||
} | ||
else { | ||
done.fail(); | ||
} | ||
}); | ||
}]; | ||
} | ||
console.log("asynquence test suite"); | ||
@@ -18,9 +60,4 @@ | ||
.val(doneLogMsg("ALL CORE TESTS PASSED!")) | ||
.val(function(){ | ||
// add the extension plugins | ||
require("./contrib.src.js"); | ||
}) | ||
.then.apply(ASQ,contrib_tests) | ||
.val(doneLogMsg("ALL CONTRIB TESTS PASSED!")) | ||
.val(doneLogMsg("ALL TESTS PASSED!")) | ||
.then.apply(ASQ,contrib_tests) // contrib tests, if any | ||
.val(doneLogMsg("\nALL TESTS PASSED!")) | ||
.or(function(){ | ||
@@ -27,0 +64,0 @@ doneLogMsg("*** TEST SUITE FAILURE ***")(); |
{ | ||
"name": "asynquence", | ||
"version": "0.2.0-c", | ||
"version": "0.3.0-a", | ||
"description": "asynquence: async sequences & gates for flow-control", | ||
@@ -8,7 +8,7 @@ "main": "./asq.js", | ||
"test": "./node-tests.js", | ||
"build-core": "./build-core.js", | ||
"bundle-contrib": "./contrib/bundle.js" | ||
"build-core": "./build-core.js" | ||
}, | ||
"devDependencies": { | ||
"uglify-js": "~2.4.8" | ||
"uglify-js": "~2.4.8", | ||
"q": "~0.9.7" | ||
}, | ||
@@ -22,3 +22,6 @@ "repository": { | ||
"flow-control", | ||
"sequences" | ||
"sequences", | ||
"promise", | ||
"iterator", | ||
"generator" | ||
], | ||
@@ -25,0 +28,0 @@ "bugs": { |
388
README.md
# asynquence | ||
A lightweight (1.6k minzipped) API for asynchronous flow control using sequences and gates. [Sequences & gates, at a glance](https://gist.github.com/getify/5959149). More advanced [example of "nested" composition of sequences](https://gist.github.com/getify/10273f3de07dda27ebce). [Example of promise & iteration styles](https://gist.github.com/jakearchibald/0e652d95c07442f205ce#comment-972613). | ||
A lightweight (**~1.6k** minzipped) micro-lib for asynchronous flow-control using sequences and gates. | ||
## Explanation | ||
### TL;DR: By Example | ||
* [Sequences & gates](https://gist.github.com/getify/5959149), at a glance. | ||
* Example/explanation of [promise-style sequences](https://gist.github.com/jakearchibald/0e652d95c07442f205ce#comment-977119). | ||
* More advanced example of ["nested" composition of sequences](https://gist.github.com/getify/10273f3de07dda27ebce). | ||
* Iterable sequences: [sync loop](http://) and [async loop](http://) | ||
* API [Usage Examples](#usage-examples) | ||
### Sequences | ||
@@ -23,5 +31,5 @@ If you want to perform two or more asynchronous tasks one after the other (like animation delays, XHR calls, etc). You want to set up an ordered series of tasks and make sure the previous one finishes before the next one is processed. You need a **sequence**. | ||
### Gates | ||
If you have two or more tasks to perform at the same time, but want to wait for them all to complete before moving on. You need a **gate**. | ||
If you have two or more tasks to perform at the same time, but want to wait for them all to complete before moving on, you need a **gate**. | ||
Calling `gate(..)` with two or more functions creates a step that is a parallel gate across those functions, such that the single step in question isn't complete until all segments of the parallel gate are complete. | ||
Calling `gate(..)` with two or more functions creates a step that is a parallel gate across those functions, such that the single step in question isn't complete until all segments of the parallel gate are **successfully** complete. | ||
@@ -35,10 +43,22 @@ For parallel gate steps, each segment of that gate will receive a copy of the message(s) passed from the previous step. Also, all messages from the segments of this gate will be passed along to the next step (or the next failure handler, in the case of a gate segment indicating a failure). | ||
`Sq.pipe(done)` is sugar short-hand for `Sq.then(done).or(done.fail)`. | ||
* `seq(..)` takes one or more functions, treating each one as a separate step in the sequence in question. These functions are expected to return new sequences, from which, in turn, both the success and failure streams will be piped back to the sequence in question. | ||
This method will also accept *asynquence* sequence instances directly. | ||
`seq(Fn)` is sugar short-hand for `then(function(done){ Fn.apply(null,[].slice.call(arguments,1)).pipe(done); })`. | ||
This method will also accept *asynquence* sequence instances directly. `seq(Sq)` is sugar short-hand for `then(function(done){ Sq.pipe(done); })`. | ||
* `val(..)` takes one or more functions, treating each one as a separate step in the sequence in question. These functions can each optionally return a value, each value of which will, in turn, be passed as the completion value for that sequence step. | ||
This method will also accept non-function values as sequence messages. | ||
`val(Fn)` is sugar short-hand for `then(function(done){ done(Fn.apply(null,[].slice.call(arguments,1))); }) | ||
This method will also accept non-function values as sequence value-messages. `val(Va)` is sugar short-hand for `then(function(done){ done(Va); })`. | ||
* `promise(..)` takes one or more [standard Promises/A+ compliant](http://promisesaplus.com/) promises, and subsumes them into the sequence. See [Promises/A+ Compliance](#promisesa-compliance) below for more information. | ||
`promise(Pr)` is sugar short-hand for `then(function(done){ Pr.then(done,done.fail); })`. | ||
This method will also accept function(s) which return promises. `promise(Fn)` is sugar short-hand for `then(function(done){ Fn.apply(null,[].slice.call(arguments,1)).then(done,done.fail); })`. | ||
You can also `abort()` a sequence at any time, which will prevent any further actions from occurring on that sequence (all callbacks will be ignored). The call to `abort()` can happen on the sequence API itself, or using the `abort` flag on a completion trigger in any step (see example below). | ||
@@ -48,2 +68,6 @@ | ||
If you want to test if any arbitrary object is an *asynquence* sequence instance, use `ASQ.isSequence(..)`. | ||
`ASQ.iterable(..)` is added by the `iterable-sequence` contrib plugin. See [Iterable Sequences](#iterable-sequences) below for more information. | ||
`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. | ||
@@ -78,16 +102,87 @@ | ||
The `/contrib/*` plugins provide a variety of [optional plugins](https://github.com/getify/asynquence/blob/master/contrib/README.md) as helpers for async flow controls. | ||
The `/contrib/*` plugins provide a variety of [optional contrib plugins](https://github.com/getify/asynquence/blob/master/contrib/README.md) as helpers for async flow-controls. | ||
For browser usage, simply include the `asq.js` library file and then the `contrib.js` file. For node.js, these contrib plugins are available as a separate npm module `asynquence-contrib`. | ||
#### Iterable Sequences | ||
One of the contrib plugins provided is `iterable-sequence`. Unlike other plugins, which add methods onto the sequence instance API, this plugin adds a new method directly onto the main module API: `ASQ.iterable(..)`. Calling `ASQ.iterable(..)` creates a special iterable sequence, as compared to calling `ASQ(..)` to create a normal *asynquence* sequence. | ||
An iterable sequence works similarly to normal *asynquence* sequences, but a bit different. `then(..)` still registers steps on the sequence, but it's basically just an alias of `val(..)`, because the most important difference is that steps of an iterable sequence **are not passed completion triggers**. | ||
Instead, an iterable sequence instance API has a `next(..)` method on it, which will allow the sequence to be externally iterated, one step at a time. Whatever is passed to `next(..)` is sent as step messages to the current step in the sequence. `next(..)` always returns an object like: | ||
```js | ||
{ | ||
value: ... // return messages | ||
done: true|false // sequence iteration complete? | ||
} | ||
``` | ||
`value` is any return message(s) from the `next(..)` invocation (`undefined` otherwise). `done` is `true` if the previously iterated step was (so far) the last registered step in the iterable sequence, or `false` if there's still more sequence steps queued up. | ||
Just like with normal *asynquence* sequences, register one or more error listeners on the iterable sequence by calling `or(..)`. If a step results in some error (either accidentally or manually via `throw ..`), the iterable sequence is flagged in the error state, and any error messages are passed to the registered `or(..)` listeners. | ||
Also, just like `next(..)` externally controls the normal iteration flow of the sequence, `throw(..)` externally "throws" an error into the iterable sequence, triggering the `or(..)` flow as above. Iterable sequences can be `abort()`d just as normal *asynquence* sequences. | ||
Iterable sequences are a special subset of sequences, and as such, many of the normal *asynquence* API variations do not exist, such as `gate(..)`, `seq(..)`, `promise(..)`, and `pipe(..)`. | ||
```js | ||
function step(num) { | ||
return "Step " + num; | ||
} | ||
var sq = ASQ.iterable() | ||
.then(step) | ||
.then(step) | ||
.then(step); | ||
for (var i=0, ret; | ||
!(ret && ret.done) && (ret = sq.next(i+1)); | ||
i++ | ||
) { | ||
console.log(ret.value); | ||
} | ||
// Step 1 | ||
// Step 2 | ||
// Step 3 | ||
``` | ||
This example shows sync iteration with a `for` loop, but of course, `next(..)` can be called in various async fashions to iterate the sequence over time. | ||
### 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. | ||
### A+ Promises | ||
**You should be able to use *asynquence* as your primary async flow control library, without the need for other Promises implementations.** | ||
### Promises/A+ Compliance | ||
**The goal of *asynquence* is that you should be able to use it as your primary async flow-control library, without the need for other Promises implementations.** | ||
This lib is intentionally designed to hide/abstract the idea of Promises, such that you can do quick and easy async flow control programming without using Promises directly. As such, *asynquence* is *not [Promises/A+](http://promisesaplus.com/) compliant*, nor *should* it be, because the "promises" used are hidden underneath *asynquence*'s API. | ||
This lib is intentionally designed to hide/abstract the idea of Promises, such that you can do quick and easy async flow-control programming without creating Promises directly. | ||
If you are already using other Promises implementations, you *can* quite easily receive and consume a regular Promise value from some other method and wire it up to signal/control flow for an *asynquence* instance. | ||
As such, the *asynquence* API itself is *not [Promises/A+](http://promisesaplus.com/) compliant*, nor *should* it be, because the "promises" used are hidden underneath *asynquence*'s API. **Note:** the implementation promises behave predictably like standard Promises. | ||
**However**, despite API similarities, an *asynquence* instance is **not** designed to be used *as a Promise value* passed to a regular Promises-based system. Trying to do so will likely cause unexpected behavior, because Promises/A+ suggests problematic (read: "dangerous") duck-typing for objects that have a `then()` method, as `asynquence` instances do. | ||
If you are also using other Promises implementations alongside *asynquence*, you *can* quite easily receive and consume a regular Promise value from some other method into the signal/control flow for an *asynquence* sequence. | ||
For example, if using both the [Q promises library](https://github.com/kriskowal/q) and *asynquence*: | ||
```js | ||
// Using *Q*, make a standard Promise out | ||
// of jQuery's Ajax "promise" | ||
var p = Q( $.ajax(..) ); | ||
// Now, asynquence flow-control including a | ||
// standard Promise | ||
ASQ() | ||
.then(function(done){ | ||
setTimeout(done,100); | ||
}) | ||
// subsume a standard Promise into the sequence | ||
.promise(p) | ||
.val(function(ajaxResp){ | ||
console.log(ajaxResp); | ||
}); | ||
``` | ||
**Despite API similarities** (like the presence of `then(..)` on the API), an *asynquence* instance is **not** designed to be used *as a Promise value* linked/passed to another standard Promise. | ||
Trying to do so will likely cause unexpected behavior, because Promises/A+ insists on problematic (read: "dangerous") duck-typing for objects that have a `then()` method, as *asynquence* instances do. | ||
## Browser, node.js (CommonJS), AMD: ready! | ||
@@ -97,2 +192,8 @@ | ||
For browser usage, simply include the `asq.js` library file. For node.js usage, install the `asynquence` package via npm, then `require(..)` the module: | ||
```js | ||
var ASQ = require("asynquence"); | ||
``` | ||
**Note:** The `ASQ.noConflict()` method really only makes sense when used in a normal browser global namespace environment. It **should not** be used when the node.js or AMD style modules are your method of inclusion. | ||
@@ -104,158 +205,183 @@ | ||
function fn1(done) { | ||
alert("Step 1"); | ||
setTimeout(done,1000); | ||
} | ||
```js | ||
function fn1(done) { | ||
alert("Step 1"); | ||
setTimeout(done,1000); | ||
} | ||
function fn2(done) { | ||
alert("Step 2"); | ||
setTimeout(done,1000); | ||
} | ||
function fn2(done) { | ||
alert("Step 2"); | ||
setTimeout(done,1000); | ||
} | ||
function yay() { | ||
alert("Done!"); | ||
} | ||
function yay() { | ||
alert("Done!"); | ||
} | ||
``` | ||
Execute `fn1`, then `fn2`, then finally `yay`: | ||
ASQ(fn1) | ||
.then(fn2) | ||
.then(yay); | ||
```js | ||
ASQ(fn1) | ||
.then(fn2) | ||
.then(yay); | ||
``` | ||
Pass messages from step to step: | ||
ASQ(function(done){ | ||
setTimeout(function(){ | ||
done("hello"); | ||
},1000); | ||
}) | ||
.then(function(done,msg1){ | ||
setTimeout(function(){ | ||
done(msg1,"world"); | ||
},1000); | ||
}) | ||
.then(function(_,msg1,msg2){ // basically ignoring this step's completion trigger (`_`) | ||
alert("Greeting: " + msg1 + " " + msg2); // 'Greeting: hello world' | ||
}); | ||
```js | ||
ASQ(function(done){ | ||
setTimeout(function(){ | ||
done("hello"); | ||
},1000); | ||
}) | ||
.then(function(done,msg1){ | ||
setTimeout(function(){ | ||
done(msg1,"world"); | ||
},1000); | ||
}) | ||
.then(function(_,msg1,msg2){ // basically ignoring this step's completion trigger (`_`) | ||
alert("Greeting: " + msg1 + " " + msg2); | ||
// 'Greeting: hello world' | ||
}); | ||
``` | ||
Handle step failure: | ||
ASQ(function(done){ | ||
setTimeout(function(){ | ||
done("hello"); | ||
},1000); | ||
}) | ||
.then(function(done,msg1){ | ||
setTimeout(function(){ | ||
done.fail(msg1,"world"); // note the `fail` flag here!! | ||
},1000); | ||
}) | ||
.then(function(){ | ||
// sequence fails, won't ever get called | ||
}) | ||
.or(function(msg1,msg2){ | ||
alert("Failure: " + msg1 + " " + msg2); // 'Failure: hello world' | ||
}); | ||
```js | ||
ASQ(function(done){ | ||
setTimeout(function(){ | ||
done("hello"); | ||
},1000); | ||
}) | ||
.then(function(done,msg1){ | ||
setTimeout(function(){ | ||
// note the `fail` flag here!! | ||
done.fail(msg1,"world"); | ||
},1000); | ||
}) | ||
.then(function(){ | ||
// sequence fails, won't ever get called | ||
}) | ||
.or(function(msg1,msg2){ | ||
alert("Failure: " + msg1 + " " + msg2); | ||
// 'Failure: hello world' | ||
}); | ||
``` | ||
Create a step that's a parallel gate: | ||
ASQ() | ||
// normal async step | ||
.then(function(done){ | ||
```js | ||
ASQ() | ||
// normal async step | ||
.then(function(done){ | ||
setTimeout(function(){ | ||
done("hello"); | ||
},1000); | ||
}) | ||
// parallel gate step (segments run in parallel) | ||
.gate( | ||
function(done,greeting){ // gate segment | ||
setTimeout(function(){ | ||
done("hello"); | ||
},1000); | ||
}) | ||
// parallel gate step (segments run in parallel) | ||
.gate( | ||
function(done,greeting){ // gate segment | ||
setTimeout(function(){ | ||
done(greeting,"world"); // <-- 2 gate messages | ||
},500); | ||
}, | ||
function(done,greeting){ // gate segment | ||
setTimeout(function(){ | ||
done(greeting + " mikey"); // <-- only 1 gate message! | ||
},100); // segment finishes first, but message still kept "in order" | ||
} | ||
) | ||
.then(function(_,msg1,msg2){ | ||
// msg1 is an array of the 2 gate messages from the first segment | ||
// msg2 is the single message (not an array) from the second segment | ||
// 2 gate messages! | ||
done(greeting,"world"); | ||
},500); | ||
}, | ||
function(done,greeting){ // gate segment | ||
setTimeout(function(){ | ||
// only 1 gate message! | ||
done(greeting + " mikey"); | ||
},100); | ||
// this segment finishes first, but message | ||
// still kept "in order" | ||
} | ||
) | ||
.then(function(_,msg1,msg2){ | ||
// msg1 is an array of the 2 gate messages | ||
// from the first segment | ||
// msg2 is the single message (not an array) | ||
// from the second segment | ||
alert("Greeting: " + msg1[0] + " " + msg1[1]); // 'Greeting: hello world' | ||
alert("Greeting: " + msg2); // 'Greeting: hello mikey' | ||
}); | ||
alert("Greeting: " + msg1[0] + " " + msg1[1]); | ||
// 'Greeting: hello world' | ||
alert("Greeting: " + msg2); | ||
// 'Greeting: hello mikey' | ||
}); | ||
``` | ||
Use `pipe(..)`, `seq(..)`, and `val(..)` helpers: | ||
var seq = ASQ() | ||
```js | ||
var seq = ASQ() | ||
.then(function(done){ | ||
ASQ() | ||
.then(function(done){ | ||
ASQ() | ||
.then(function(done){ | ||
setTimeout(function(){ | ||
done("Hello World"); | ||
},100); | ||
}) | ||
.pipe(done); // pipe sequence output to `done` completion trigger | ||
setTimeout(function(){ | ||
done("Hello World"); | ||
},100); | ||
}) | ||
.val(function(msg){ // NOTE: no completion trigger passed in! | ||
return msg.toUpperCase(); // map return value as step output | ||
}) | ||
.seq(function(msg){ // NOTE: no completion trigger passed in! | ||
var seq = ASQ(); | ||
.pipe(done); // pipe sequence output to `done` completion trigger | ||
}) | ||
.val(function(msg){ // NOTE: no completion trigger passed in! | ||
return msg.toUpperCase(); // map return value as step output | ||
}) | ||
.seq(function(msg){ // NOTE: no completion trigger passed in! | ||
var seq = ASQ(); | ||
seq | ||
.then(function(done){ | ||
setTimeout(function(){ | ||
done(msg.split(" ")[0]); | ||
},100); | ||
}); | ||
return seq; // pipe this sub-sequence back into the main sequence | ||
}) | ||
.then(function(_,msg){ | ||
alert(msg); // "HELLO" | ||
seq | ||
.then(function(done){ | ||
setTimeout(function(){ | ||
done(msg.split(" ")[0]); | ||
},100); | ||
}); | ||
return seq; // pipe this sub-sequence back into the main sequence | ||
}) | ||
.then(function(_,msg){ | ||
alert(msg); // "HELLO" | ||
}); | ||
``` | ||
Abort a sequence in progress: | ||
var seq = ASQ() | ||
.then(fn1) | ||
.then(fn2) | ||
.then(yay); | ||
```js | ||
var seq = ASQ() | ||
.then(fn1) | ||
.then(fn2) | ||
.then(yay); | ||
setTimeout(function(){ | ||
// will stop the sequence before running | ||
// steps `fn2` and `yay` | ||
seq.abort(); | ||
},100); | ||
// same as above | ||
ASQ() | ||
.then(fn1) | ||
.then(function(done){ | ||
setTimeout(function(){ | ||
seq.abort(); // will stop the sequence before running steps `fn2` and `yay` | ||
// `abort` flag will stop the sequence | ||
// before running steps `fn2` and `yay` | ||
done.abort(); | ||
},100); | ||
}) | ||
.then(fn2) | ||
.then(yay); | ||
``` | ||
// same as above | ||
ASQ() | ||
.then(fn1) | ||
.then(function(done){ | ||
setTimeout(function(){ | ||
done.abort(); // `abort` flag will stop the sequence before running steps `fn2` and `yay` | ||
},100); | ||
}) | ||
.then(fn2) | ||
.then(yay); | ||
## Builds | ||
There are two utilities included which you can use to build the files. | ||
The core library file can be built (minified) with an included utility: | ||
* `./build-core.js` builds (minifies) the `asq.js` file (in the package root). The recommended way to invoke this utility is via npm: | ||
``` | ||
./build-core.js | ||
``` | ||
`npm run-script build-core` | ||
However, the recommended way to invoke this utility is via npm: | ||
* `contrib/bundle.js` builds `contrib.src.js` (in the package root), and then builds (minifies) `contrib.js` (in the package root). The recommended way to invoke this utility is via npm: | ||
``` | ||
npm run-script build-core | ||
``` | ||
`npm run-script bundle-contrib` | ||
By default, the build includes all the `contrib/plugin.*` plugins. You can manually specify which plugins you want, like this: | ||
`contrib/bundle.js any none try` (which would bundle only `any`, `none`, and `try` plugins) | ||
**Note:** `npm run-script ..` [doesn't *currently*](https://github.com/isaacs/npm/issues/3494) support passing the extra command line params, so you must use `contrib/bundle.js` instead of `npm run-script bundle-contrib` if you want to pick which plugins to bundle. | ||
## License | ||
@@ -262,0 +388,0 @@ |
81
tests.js
@@ -844,4 +844,81 @@ (function(name,context,dependency,definition){ | ||
tests.push(function(testDone){ | ||
var label = "Core Test #20", timeout; | ||
var label = "Core Test #20", timeout, Q = tests.Q; | ||
function Pr(){ | ||
var args = ARRAY_SLICE.call(arguments); | ||
var def = Q.defer(); | ||
setTimeout(function(){ | ||
def.resolve(args.length > 1 ? args : args[0]); | ||
},50); | ||
return def.promise; | ||
} | ||
function Br(){ | ||
var args = ARRAY_SLICE.call(arguments); | ||
var def = Q.defer(); | ||
setTimeout(function(){ | ||
def.reject(args.length > 1 ? args : args[0]); | ||
},50); | ||
return def.promise; | ||
} | ||
ASQ("Hello") | ||
// generate 3 promises in succession, | ||
// using asynquence to chain them | ||
.promise(Pr,Pr,Pr) | ||
.val(function(msg){ | ||
if (!( | ||
arguments.length === 1 && | ||
msg === "Hello" | ||
)) { | ||
var args = ARRAY_SLICE.call(arguments); | ||
args.unshift(testDone,label); | ||
FAIL.apply(FAIL,args); | ||
} | ||
}) | ||
.promise(Pr("Hello","World"),Pr) | ||
.val(function(msg){ | ||
if (!( | ||
arguments.length === 1 && | ||
Array.isArray(msg) && | ||
msg.length === 2 && | ||
msg[0] === "Hello" && | ||
msg[1] === "World" | ||
)) { | ||
var args = ARRAY_SLICE.call(arguments); | ||
args.unshift(testDone,label); | ||
FAIL.apply(FAIL,args); | ||
} | ||
return msg[1].toUpperCase(); | ||
}) | ||
.promise(Br) // Note: a broken promise! | ||
.val(function(msg1,msg2){ | ||
clearTimeout(timeout); | ||
var args = ARRAY_SLICE.call(arguments); | ||
args.unshift(testDone,label); | ||
FAIL.apply(FAIL,args); | ||
}) | ||
.or(function(msg){ | ||
clearTimeout(timeout); | ||
if (arguments.length === 1 && | ||
msg === "WORLD" | ||
) { | ||
PASS(testDone,label); | ||
} | ||
else { | ||
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 = "Core Test #21", timeout; | ||
// testing a custom plugin which will pass along | ||
@@ -860,3 +937,3 @@ // any messages received to it, but will inject | ||
done.apply(null, | ||
Array.prototype.slice.call(arguments,1) | ||
ARRAY_SLICE.call(arguments,1) | ||
.concat(["bar"]) | ||
@@ -863,0 +940,0 @@ ); |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
1
386
4
60682
2
9
1528