├── contrib ├── .npmignore ├── .editorconfig ├── plugin.failAfter.js ├── plugin.toPromise.js ├── plugin.after.js ├── plugin.errfcb.js ├── plugin.waterfall.js ├── plugin.race.js ├── plugin.try.js ├── node-tests.js ├── plugin.until.js ├── fake_writable_stream.js ├── plugin.map.js ├── package.json ├── test-extensions-1.js ├── test-extensions-2.js ├── plugin.first.js ├── plugin.last.js ├── plugin.any.js ├── plugin.none.js ├── plugin.react.js ├── plugin.wrap.js ├── EventEmitter.min.js ├── contrib-wrapper.js ├── plugin.pThenCatch.js ├── plugin.iterable.js ├── bundle.js ├── plugin.runner.js ├── plugin.reactHelpers.js ├── plugin.goCSP.js └── README.md ├── .npmignore ├── .gitignore ├── .editorconfig ├── .github └── FUNDING.yml ├── package.json ├── tests.html ├── node-tests.js ├── legacy.js ├── asq.src.js ├── README.md └── tests.js /contrib/.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | node_modules 3 | contrib 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | asq.js 3 | contrib.src.js 4 | contrib.js 5 | contrib-common.src.js 6 | contrib-common.js 7 | contrib-es6.src.js 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_style = tab 8 | indent_size = 4 9 | 10 | [*.md] 11 | indent_style = space 12 | indent_size = 4 13 | -------------------------------------------------------------------------------- /contrib/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_style = tab 8 | indent_size = 4 9 | 10 | [*.md] 11 | indent_style = space 12 | indent_size = 4 13 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [getify] 4 | patreon: getify 5 | custom: ['https://www.paypal.com/paypalme2/getify','https://www.blockchain.com/btc/payment_request?address=3GrZuzooWAAjufEydb7c8MrUYznCiHHT9U&message=getify+Support+Donation'] 6 | -------------------------------------------------------------------------------- /contrib/plugin.failAfter.js: -------------------------------------------------------------------------------- 1 | // "failAfter" 2 | ASQ.extend("failAfter",function $$extend(api,internals){ 3 | return function $$failAfter(num) { 4 | var args = arguments.length > 1 ? 5 | ARRAY_SLICE.call(arguments,1) : 6 | void 0 7 | ; 8 | num = +num || 0; 9 | 10 | api.then(function $$then(done){ 11 | setTimeout(function $$set$timeout(){ 12 | done.fail.apply(ø,args); 13 | },num); 14 | }); 15 | 16 | return api; 17 | }; 18 | }); 19 | 20 | ASQ.failAfter = function $$fail$after() { 21 | return ASQ().failAfter.apply(ø,arguments); 22 | }; 23 | -------------------------------------------------------------------------------- /contrib/plugin.toPromise.js: -------------------------------------------------------------------------------- 1 | // "toPromise" 2 | ASQ.extend("toPromise",function $$extend(api,internals){ 3 | return function $$to$promise() { 4 | return new Promise(function $$executor(resolve,reject){ 5 | api 6 | .val(function $$val(){ 7 | var args = ARRAY_SLICE.call(arguments); 8 | resolve.call(ø,args.length > 1 ? args : args[0]); 9 | return ASQ.messages.apply(ø,args); 10 | }) 11 | .or(function $$or(){ 12 | var args = ARRAY_SLICE.call(arguments); 13 | reject.call(ø,args.length > 1 ? args : args[0]); 14 | }); 15 | }); 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /contrib/plugin.after.js: -------------------------------------------------------------------------------- 1 | // "after" 2 | ASQ.extend("after",function $$extend(api,internals){ 3 | return function $$after(num) { 4 | var orig_args = arguments.length > 1 ? 5 | ARRAY_SLICE.call(arguments,1) : 6 | void 0 7 | ; 8 | num = +num || 0; 9 | 10 | api.then(function $$then(done){ 11 | var args = orig_args || ARRAY_SLICE.call(arguments,1); 12 | 13 | setTimeout(function $$set$timeout(){ 14 | done.apply(ø,args); 15 | },num); 16 | }); 17 | 18 | return api; 19 | }; 20 | }); 21 | 22 | ASQ.after = function $$after() { 23 | return ASQ().after.apply(ø,arguments); 24 | }; 25 | -------------------------------------------------------------------------------- /contrib/plugin.errfcb.js: -------------------------------------------------------------------------------- 1 | // "errfcb" 2 | ASQ.extend("errfcb",function $$extend(api,internals){ 3 | return function $$errfcb() { 4 | // create a fake sequence to extract the callbacks 5 | var sq = { 6 | val: function $$then(cb){ sq.val_cb = cb; return sq; }, 7 | or: function $$or(cb){ sq.or_cb = cb; return sq; } 8 | }; 9 | 10 | // trick `seq(..)`s checks for a sequence 11 | sq[brand] = true; 12 | 13 | // immediately register our fake sequence on the 14 | // main sequence 15 | api.seq(sq); 16 | 17 | // provide the "error-first" callback 18 | return function $$errorfirst$callback(err) { 19 | if (err) { 20 | sq.or_cb(err); 21 | } 22 | else { 23 | sq.val_cb.apply(ø,ARRAY_SLICE.call(arguments,1)); 24 | } 25 | }; 26 | }; 27 | }); 28 | -------------------------------------------------------------------------------- /contrib/plugin.waterfall.js: -------------------------------------------------------------------------------- 1 | // "waterfall" 2 | ASQ.extend("waterfall",function $$extend(api,internals){ 3 | return function $$waterfall() { 4 | if (internals("seq_error") || internals("seq_aborted") || 5 | arguments.length === 0 6 | ) { 7 | return api; 8 | } 9 | 10 | var fns = ARRAY_SLICE.call(arguments); 11 | 12 | api.then(function $$then(done){ 13 | var msgs = ASQ.messages(), 14 | sq = ASQ.apply(ø,ARRAY_SLICE.call(arguments,1)) 15 | ; 16 | 17 | fns.forEach(function $$each(fn){ 18 | sq.then(fn) 19 | .val(function $$val(){ 20 | var args = ASQ.messages.apply(ø,arguments); 21 | msgs.push(args.length > 1 ? args : args[0]); 22 | return msgs; 23 | }); 24 | }); 25 | 26 | sq.pipe(done); 27 | }); 28 | 29 | return api; 30 | }; 31 | }); 32 | -------------------------------------------------------------------------------- /contrib/plugin.race.js: -------------------------------------------------------------------------------- 1 | // "race" 2 | ASQ.extend("race",function $$extend(api,internals){ 3 | return function $$race() { 4 | if (internals("seq_error") || internals("seq_aborted") || 5 | arguments.length === 0 6 | ) { 7 | return api; 8 | } 9 | 10 | var fns = ARRAY_SLICE.call(arguments) 11 | .map(function $$map(v){ 12 | var def; 13 | // tap any directly-provided sequences immediately 14 | if (ASQ.isSequence(v)) { 15 | def = { seq: v }; 16 | tapSequence(def); 17 | return function $$fn(done) { 18 | def.seq.pipe(done); 19 | }; 20 | } 21 | else return v; 22 | }); 23 | 24 | api.then(function $$then(done){ 25 | var args = ARRAY_SLICE.call(arguments); 26 | 27 | fns.forEach(function $$each(fn){ 28 | fn.apply(ø,args); 29 | }); 30 | }); 31 | 32 | return api; 33 | }; 34 | }); 35 | -------------------------------------------------------------------------------- /contrib/plugin.try.js: -------------------------------------------------------------------------------- 1 | // "try" 2 | ASQ.extend("try",function $$extend(api,internals){ 3 | return function $$try() { 4 | if (internals("seq_error") || internals("seq_aborted") || 5 | arguments.length === 0 6 | ) { 7 | return api; 8 | } 9 | 10 | var fns = ARRAY_SLICE.call(arguments) 11 | .map(function $$map(fn){ 12 | return function $$then(mainDone) { 13 | var main_args = ARRAY_SLICE.call(arguments), 14 | sq = ASQ.apply(ø,main_args.slice(1)) 15 | ; 16 | 17 | sq 18 | .then(function $$inner$then(){ 19 | fn.apply(ø,arguments); 20 | }) 21 | .val(function $$val(){ 22 | mainDone.apply(ø,arguments); 23 | }) 24 | .or(function $$inner$or(){ 25 | var msgs = ASQ.messages.apply(ø,arguments); 26 | // failed, so map error(s) as `catch` 27 | mainDone({ 28 | "catch": msgs.length > 1 ? msgs : msgs[0] 29 | }); 30 | }); 31 | }; 32 | }); 33 | 34 | api.then.apply(ø,fns); 35 | 36 | return api; 37 | }; 38 | }); 39 | -------------------------------------------------------------------------------- /contrib/node-tests.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | function doneLogMsg(msg) { 4 | return function() { 5 | console.log(msg); 6 | }; 7 | } 8 | 9 | require("native-promise-only"); 10 | var path = require("path"); 11 | 12 | // NOTE: don't need to import "asynquence" explicitly here, since 13 | // the contrib package gets it for us 14 | var ASQ = require( path.join(__dirname,"contrib.src.js") ); 15 | try { ASQ.messages(); } catch (err) { 16 | ASQ = ASQ( require(path.join("..","asq.js")) ); 17 | } 18 | 19 | var tests = require(path.join(__dirname,"tests.js"))(ASQ,doneLogMsg); 20 | 21 | console.log("asynquence-contrib test suite"); 22 | 23 | ASQ.apply(ASQ,tests) 24 | .val(doneLogMsg("ALL CONTRIB TESTS PASSED!")) 25 | .or(function(){ 26 | doneLogMsg("*** TEST SUITE FAILURE ***")(); 27 | for (var i=0; i", 36 | "license": "MIT" 37 | } 38 | -------------------------------------------------------------------------------- /contrib/plugin.until.js: -------------------------------------------------------------------------------- 1 | // "until" 2 | ASQ.extend("until",function $$extend(api,internals){ 3 | return function $$until() { 4 | if (internals("seq_error") || internals("seq_aborted") || 5 | arguments.length === 0 6 | ) { 7 | return api; 8 | } 9 | 10 | var fns = ARRAY_SLICE.call(arguments) 11 | .map(function $$map(fn){ 12 | return function $$then(mainDone) { 13 | var main_args = ARRAY_SLICE.call(arguments), 14 | sq = ASQ.apply(ø,main_args.slice(1)) 15 | ; 16 | 17 | sq 18 | .then(function $$inner$then(){ 19 | var args = ARRAY_SLICE.call(arguments); 20 | args[0]["break"] = function $$break(){ 21 | mainDone.fail.apply(ø,arguments); 22 | sq.abort(); 23 | }; 24 | 25 | fn.apply(ø,args); 26 | }) 27 | .val(function $$val(){ 28 | mainDone.apply(ø,arguments); 29 | }) 30 | .or(function $$inner$or(){ 31 | // failed, retry 32 | $$then.apply(ø,main_args); 33 | }); 34 | }; 35 | }); 36 | 37 | api.then.apply(ø,fns); 38 | 39 | return api; 40 | }; 41 | }); 42 | -------------------------------------------------------------------------------- /contrib/fake_writable_stream.js: -------------------------------------------------------------------------------- 1 | (function UMD(name,context,definition){ 2 | // special form of UMD for using a "global" across evironments 3 | context[name] = context[name] || definition(); 4 | if (typeof module !== "undefined" && module.exports) { module.exports = context[name]; } 5 | else if (typeof define === "function" && define.amd) { define(function $AMD$(){ return context[name]; }); } 6 | })("fakeWritableStream",typeof global !== "undefined" ? global : this,function DEF(){ 7 | 8 | 9 | // fake minimal writable streams implementation for 10 | // non-node testing purposes only 11 | function fakeWritableStream() { 12 | 13 | function write(chunk,_,cb) { 14 | this.emit("data",chunk); 15 | cb(); 16 | return true; 17 | } 18 | 19 | function end(chunk,_,cb) { 20 | if (chunk) { 21 | this.emit("data",chunk); 22 | this.emit("end"); 23 | cb(); 24 | } 25 | else { 26 | cb(); 27 | this.emit("end"); 28 | } 29 | } 30 | 31 | var publicAPI = new EventEmitter(); 32 | publicAPI.write = write; 33 | publicAPI.end = end; 34 | 35 | return publicAPI; 36 | } 37 | 38 | return fakeWritableStream; 39 | 40 | }); 41 | -------------------------------------------------------------------------------- /contrib/plugin.map.js: -------------------------------------------------------------------------------- 1 | // "map" 2 | ASQ.extend("map",function $$extend(api,internals){ 3 | return function $$map(pArr,pEach) { 4 | if (internals("seq_error") || internals("seq_aborted")) { 5 | return api; 6 | } 7 | 8 | api.seq(function $$seq(){ 9 | var tmp, args = ARRAY_SLICE.call(arguments), 10 | arr = pArr, each = pEach; 11 | 12 | // if missing `map(..)` args, use value-messages (if any) 13 | if (!each) each = args.shift(); 14 | if (!arr) arr = args.shift(); 15 | 16 | // if arg types in reverse order (each,arr), swap 17 | if (typeof arr === "function" && Array.isArray(each)) { 18 | tmp = arr; 19 | arr = each; 20 | each = tmp; 21 | } 22 | 23 | return ASQ.apply(ø,args) 24 | .gate.apply(ø,arr.map(function $$map(item){ 25 | return function $$segment(){ 26 | each.apply(ø,[item].concat(ARRAY_SLICE.call(arguments))); 27 | }; 28 | })); 29 | }) 30 | .val(function $$val(){ 31 | // collect all gate segment output into one value-message 32 | // Note: return a normal array here, not a message wrapper! 33 | return ARRAY_SLICE.call(arguments); 34 | }); 35 | 36 | return api; 37 | }; 38 | }); 39 | -------------------------------------------------------------------------------- /contrib/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "asynquence-contrib", 3 | "version": "0.28.2", 4 | "description": "additional plugins for asynquence", 5 | "main": "./contrib.src.js", 6 | "scripts": { 7 | "test": "./node-tests.js", 8 | "bundle": "./bundle.js", 9 | "bundle-es6": "npm run bundle -- --keep-es6 --bundle=contrib-es6.src.js", 10 | "bundle-common": "npm run bundle -- --bundle=contrib-common.src.js --min-bundle=contrib-common.js after iterable race runner toPromise wrap", 11 | "build": "npm run bundle && npm run bundle-es6 && npm run bundle-common", 12 | "prepublish": "npm run build" 13 | }, 14 | "devDependencies": { 15 | "uglify-js": "~2.4.24", 16 | "native-promise-only": "latest", 17 | "minimist": "~0.2.0", 18 | "freshy": "~0.0.2", 19 | "es-feature-tests": "latest", 20 | "babel-core": "~5.8.12" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "git://github.com/getify/asynquence.git" 25 | }, 26 | "keywords": [ 27 | "async", 28 | "flow-control", 29 | "sequences", 30 | "promise", 31 | "iterator", 32 | "generator" 33 | ], 34 | "bugs": { 35 | "url": "https://github.com/getify/asynquence/issues", 36 | "email": "getify@gmail.com" 37 | }, 38 | "homepage": "https://github.com/getify/asynquence/blob/master/contrib", 39 | "author": "Kyle Simpson ", 40 | "license": "MIT" 41 | } 42 | -------------------------------------------------------------------------------- /contrib/test-extensions-1.js: -------------------------------------------------------------------------------- 1 | (function UMD(dependency,definition){ 2 | if (typeof module !== "undefined" && module.exports) { 3 | // make dependency injection wrapper first 4 | module.exports = function $InjectDependency$(dep) { 5 | // only try to `require(..)` if dependency is a string module path 6 | if (typeof dep == "string") { 7 | try { dep = require(dep); } 8 | catch (err) { 9 | // dependency not yet fulfilled, so just return 10 | // dependency injection wrapper again 11 | return $InjectDependency$; 12 | } 13 | } 14 | return definition(dep); 15 | }; 16 | 17 | // if possible, immediately try to resolve wrapper 18 | // (with peer dependency) 19 | if (typeof dependency == "string") { 20 | module.exports = module.exports( require("path").join("..",dependency) ); 21 | } 22 | } 23 | else if (typeof define == "function" && define.amd) { define([dependency],definition); } 24 | else { definition(dependency); } 25 | })(this.ASQ || "asynquence",function DEF(ASQ){ 26 | "use strict"; 27 | 28 | var ARRAY_SLICE = Array.prototype.slice, 29 | ø = Object.create(null), 30 | brand = "__ASQ__", 31 | schedule = ASQ.__schedule, 32 | tapSequence = ASQ.__tapSequence 33 | ; 34 | 35 | ASQ.extend("foobar",function(){ return function(){}; }); 36 | 37 | // just return `ASQ` itself for convenience sake 38 | return ASQ; 39 | }); 40 | -------------------------------------------------------------------------------- /contrib/test-extensions-2.js: -------------------------------------------------------------------------------- 1 | (function UMD(dependency,definition){ 2 | if (typeof module !== "undefined" && module.exports) { 3 | // make dependency injection wrapper first 4 | module.exports = function $InjectDependency$(dep) { 5 | // only try to `require(..)` if dependency is a string module path 6 | if (typeof dep == "string") { 7 | try { dep = require(dep); } 8 | catch (err) { 9 | // dependency not yet fulfilled, so just return 10 | // dependency injection wrapper again 11 | return $InjectDependency$; 12 | } 13 | } 14 | return definition(dep); 15 | }; 16 | 17 | // if possible, immediately try to resolve wrapper 18 | // (with peer dependency) 19 | if (typeof dependency == "string") { 20 | module.exports = module.exports( require("path").join("..",dependency) ); 21 | } 22 | } 23 | else if (typeof define == "function" && define.amd) { define([dependency],definition); } 24 | else { definition(dependency); } 25 | })(this.ASQ || "asynquence",function DEF(ASQ){ 26 | "use strict"; 27 | 28 | var ARRAY_SLICE = Array.prototype.slice, 29 | ø = Object.create(null), 30 | brand = "__ASQ__", 31 | schedule = ASQ.__schedule, 32 | tapSequence = ASQ.__tapSequence 33 | ; 34 | 35 | ASQ.extend("bazbam",function(){ return function(){}; }); 36 | 37 | // just return `ASQ` itself for convenience sake 38 | return ASQ; 39 | }); 40 | -------------------------------------------------------------------------------- /tests.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | asynquence test suite 6 | 7 | 8 | 9 |

asynquence test suite

10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /contrib/plugin.first.js: -------------------------------------------------------------------------------- 1 | // "first" 2 | ASQ.extend("first",function $$extend(api,internals){ 3 | return function $$first() { 4 | if (internals("seq_error") || internals("seq_aborted") || 5 | arguments.length === 0 6 | ) { 7 | return api; 8 | } 9 | 10 | var fns = ARRAY_SLICE.call(arguments); 11 | 12 | api.then(function $$then(done){ 13 | function reset() { 14 | error_messages.length = 0; 15 | } 16 | 17 | function success(trigger,idx,args) { 18 | if (!finished) { 19 | finished = true; 20 | 21 | // first successful segment triggers 22 | // main sequence to proceed as success 23 | trigger( 24 | args.length > 1 ? 25 | ASQ.messages.apply(ø,args) : 26 | args[0] 27 | ); 28 | 29 | reset(); 30 | } 31 | } 32 | 33 | function failure(trigger,idx,args) { 34 | if (!finished && 35 | !(idx in error_messages) 36 | ) { 37 | completed++; 38 | error_messages[idx] = 39 | args.length > 1 ? 40 | ASQ.messages.apply(ø,args) : 41 | args[0] 42 | ; 43 | 44 | // all segments complete without success? 45 | if (completed === fns.length) { 46 | finished = true; 47 | 48 | // send errors into main sequence 49 | error_messages.length = fns.length; 50 | trigger.fail.apply(ø,error_messages); 51 | 52 | reset(); 53 | } 54 | } 55 | } 56 | 57 | var completed = 0, error_messages = [], finished = false, 58 | sq = ASQ.apply(ø,ARRAY_SLICE.call(arguments,1)) 59 | ; 60 | 61 | wrapGate(sq,fns,success,failure,reset); 62 | 63 | sq.pipe(done); 64 | }); 65 | 66 | return api; 67 | }; 68 | }); 69 | -------------------------------------------------------------------------------- /node-tests.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | function doneLogMsg(msg) { 4 | return function() { 5 | console.log(msg); 6 | }; 7 | } 8 | 9 | require("native-promise-only"); 10 | var path = require("path"); 11 | var ASQ = require(path.join(__dirname,"asq.src.js")); 12 | var tests = require(path.join(__dirname,"tests.js"))(doneLogMsg); 13 | var fs = require("fs"); 14 | var child_process = require("child_process"); 15 | var contrib_tests_file = path.join(__dirname,"contrib","node-tests.js"); 16 | var contrib_tests = [function(done){ 17 | console.log("*** CONTRIB TESTS SKIPPED ***"); 18 | done(); 19 | }]; 20 | 21 | 22 | // if `./contrib/` exists, run the contrib test suite 23 | if (fs.existsSync(contrib_tests_file)) { 24 | contrib_tests = [function __contrib_tests__(done) { 25 | console.log(""); 26 | var cp = child_process.spawn( 27 | contrib_tests_file, 28 | /*args=*/[], 29 | { 30 | cwd: path.join(__dirname,"contrib"), 31 | env: process.env, 32 | encoding: "utf8" 33 | } 34 | ); 35 | 36 | cp.stdout.on("data",function(data){ 37 | console.log(data.toString().replace(/\n$/,"")); 38 | }); 39 | cp.stderr.on("data",function(data){ 40 | console.error(data.toString().replace(/\n$/,"")); 41 | }); 42 | cp.on("close",function(code){ 43 | if (code === 0) { 44 | done(); 45 | } 46 | else { 47 | done.fail(); 48 | } 49 | }); 50 | }]; 51 | } 52 | 53 | 54 | console.log("asynquence test suite"); 55 | 56 | ASQ.apply(ASQ,tests) 57 | .val(doneLogMsg("ALL CORE TESTS PASSED!")) 58 | .then.apply(ASQ,contrib_tests) // contrib tests, if any 59 | .val(doneLogMsg("\nALL TESTS PASSED!")) 60 | .or(function(){ 61 | doneLogMsg("*** TEST SUITE FAILURE ***")(); 62 | for (var i=0; i 1 ? 25 | ASQ.messages.apply(ø,success_messages) : 26 | success_messages[0] 27 | ); 28 | } 29 | else { 30 | // send errors into main sequence 31 | error_messages.length = fns.length; 32 | trigger.fail.apply(ø,error_messages); 33 | } 34 | 35 | reset(); 36 | } 37 | 38 | function success(trigger,idx,args) { 39 | if (!finished) { 40 | completed++; 41 | success_messages = args; 42 | 43 | // all segments complete? 44 | if (completed === fns.length) { 45 | finished = true; 46 | 47 | complete(trigger); 48 | } 49 | } 50 | } 51 | 52 | function failure(trigger,idx,args) { 53 | if (!finished && 54 | !(idx in error_messages) 55 | ) { 56 | completed++; 57 | error_messages[idx] = 58 | args.length > 1 ? 59 | ASQ.messages.apply(ø,args) : 60 | args[0] 61 | ; 62 | } 63 | 64 | // all segments complete? 65 | if (!finished && 66 | completed === fns.length 67 | ) { 68 | finished = true; 69 | 70 | complete(trigger); 71 | } 72 | } 73 | 74 | var completed = 0, error_messages = [], finished = false, 75 | sq = ASQ.apply(ø,ARRAY_SLICE.call(arguments,1)), 76 | success_messages 77 | ; 78 | 79 | wrapGate(sq,fns,success,failure,reset); 80 | 81 | sq.pipe(done); 82 | }); 83 | 84 | return api; 85 | }; 86 | }); 87 | -------------------------------------------------------------------------------- /contrib/plugin.any.js: -------------------------------------------------------------------------------- 1 | // "any" 2 | ASQ.extend("any",function $$extend(api,internals){ 3 | return function $$any() { 4 | if (internals("seq_error") || internals("seq_aborted") || 5 | arguments.length === 0 6 | ) { 7 | return api; 8 | } 9 | 10 | var fns = ARRAY_SLICE.call(arguments); 11 | 12 | api.then(function $$then(done){ 13 | function reset() { 14 | finished = true; 15 | error_messages.length = 0; 16 | success_messages.length = 0; 17 | } 18 | 19 | function complete(trigger) { 20 | if (success_messages.length > 0) { 21 | // any successful segment's message(s) sent 22 | // to main sequence to proceed as success 23 | success_messages.length = fns.length; 24 | trigger.apply(ø,success_messages); 25 | } 26 | else { 27 | // send errors into main sequence 28 | error_messages.length = fns.length; 29 | trigger.fail.apply(ø,error_messages); 30 | } 31 | 32 | reset(); 33 | } 34 | 35 | function success(trigger,idx,args) { 36 | if (!finished) { 37 | completed++; 38 | success_messages[idx] = 39 | args.length > 1 ? 40 | ASQ.messages.apply(ø,args) : 41 | args[0] 42 | ; 43 | 44 | // all segments complete? 45 | if (completed === fns.length) { 46 | finished = true; 47 | 48 | complete(trigger); 49 | } 50 | } 51 | } 52 | 53 | function failure(trigger,idx,args) { 54 | if (!finished && 55 | !(idx in error_messages) 56 | ) { 57 | completed++; 58 | error_messages[idx] = 59 | args.length > 1 ? 60 | ASQ.messages.apply(ø,args) : 61 | args[0] 62 | ; 63 | } 64 | 65 | // all segments complete? 66 | if (!finished && 67 | completed === fns.length 68 | ) { 69 | finished = true; 70 | 71 | complete(trigger); 72 | } 73 | } 74 | 75 | var completed = 0, error_messages = [], finished = false, 76 | success_messages = [], 77 | sq = ASQ.apply(ø,ARRAY_SLICE.call(arguments,1)) 78 | ; 79 | 80 | wrapGate(sq,fns,success,failure,reset); 81 | 82 | sq.pipe(done); 83 | }); 84 | 85 | return api; 86 | }; 87 | }); 88 | -------------------------------------------------------------------------------- /contrib/plugin.none.js: -------------------------------------------------------------------------------- 1 | // "none" 2 | ASQ.extend("none",function $$extend(api,internals){ 3 | return function $$none() { 4 | if (internals("seq_error") || internals("seq_aborted") || 5 | arguments.length === 0 6 | ) { 7 | return api; 8 | } 9 | 10 | var fns = ARRAY_SLICE.call(arguments); 11 | 12 | api.then(function $$then(done){ 13 | function reset() { 14 | finished = true; 15 | error_messages.length = 0; 16 | success_messages.length = 0; 17 | } 18 | 19 | function complete(trigger) { 20 | if (success_messages.length > 0) { 21 | // any successful segment's message(s) sent 22 | // to main sequence to proceed as **error** 23 | success_messages.length = fns.length; 24 | trigger.fail.apply(ø,success_messages); 25 | } 26 | else { 27 | // send errors as **success** to main sequence 28 | error_messages.length = fns.length; 29 | trigger.apply(ø,error_messages); 30 | } 31 | 32 | reset(); 33 | } 34 | 35 | function success(trigger,idx,args) { 36 | if (!finished) { 37 | completed++; 38 | success_messages[idx] = 39 | args.length > 1 ? 40 | ASQ.messages.apply(ø,args) : 41 | args[0] 42 | ; 43 | 44 | // all segments complete? 45 | if (completed === fns.length) { 46 | finished = true; 47 | 48 | complete(trigger); 49 | } 50 | } 51 | } 52 | 53 | function failure(trigger,idx,args) { 54 | if (!finished && 55 | !(idx in error_messages) 56 | ) { 57 | completed++; 58 | error_messages[idx] = 59 | args.length > 1 ? 60 | ASQ.messages.apply(ø,args) : 61 | args[0] 62 | ; 63 | } 64 | 65 | // all segments complete? 66 | if (!finished && 67 | completed === fns.length 68 | ) { 69 | finished = true; 70 | 71 | complete(trigger); 72 | } 73 | } 74 | 75 | var completed = 0, error_messages = [], finished = false, 76 | sq = ASQ.apply(ø,ARRAY_SLICE.call(arguments,1)), 77 | success_messages = [] 78 | ; 79 | 80 | wrapGate(sq,fns,success,failure,reset); 81 | 82 | sq.pipe(done); 83 | }); 84 | 85 | return api; 86 | }; 87 | }); 88 | -------------------------------------------------------------------------------- /contrib/plugin.react.js: -------------------------------------------------------------------------------- 1 | // "react" (reactive sequences) 2 | (function IIFE(){ 3 | 4 | var extensions = {}; 5 | 6 | ASQ.react = function $$react(reactor) { 7 | function next() { 8 | if (!paused) { 9 | if (template) { 10 | var sq = template.duplicate(); 11 | sq.unpause.apply(ø,arguments); 12 | return sq; 13 | } 14 | return ASQ(function $$asq(){ throw "Disabled Sequence"; }); 15 | } 16 | } 17 | 18 | function registerTeardown(fn) { 19 | if (template && typeof fn === "function") { 20 | teardowns.push(fn); 21 | } 22 | } 23 | 24 | var template = ASQ().duplicate(), 25 | teardowns = [], paused = false 26 | ; 27 | 28 | // add reactive sequence kill switch 29 | template.stop = function $$stop() { 30 | if (template) { 31 | template = null; 32 | teardowns.forEach(Function.call,Function.call); 33 | teardowns.length = 0; 34 | } 35 | }; 36 | 37 | template.pause = function $$pause() { 38 | if (!paused && template) { 39 | paused = true; 40 | teardowns.forEach(Function.call,Function.call); 41 | teardowns.length = 0; 42 | } 43 | }; 44 | 45 | template.resume = function $$resume() { 46 | if (paused && template) { 47 | paused = false; 48 | reactor.call(template,next,registerTeardown); 49 | } 50 | }; 51 | 52 | template.push = next; 53 | 54 | next.onStream = function $$onStream() { 55 | ARRAY_SLICE.call(arguments) 56 | .forEach(function $$each(stream){ 57 | stream.on("data",next); 58 | stream.on("error",next); 59 | }); 60 | }; 61 | 62 | next.unStream = function $$unStream() { 63 | ARRAY_SLICE.call(arguments) 64 | .forEach(function $$each(stream){ 65 | stream.removeListener("data",next); 66 | stream.removeListener("error",next); 67 | }); 68 | }; 69 | 70 | Object.keys(extensions) 71 | .forEach(function $$each(name){ 72 | template[name] = template[name] || extensions[name](template); 73 | }); 74 | 75 | // blacklist (remove from reactive sequences) 76 | ["pipe","fork","errfcb","pThen","pCatch","toPromise"] 77 | .forEach(function $$each(name){ 78 | delete template[name]; 79 | }); 80 | 81 | // make sure `reactor(..)` is called async 82 | ASQ.__schedule(function $$schedule(){ 83 | reactor.call(template,next,registerTeardown); 84 | }); 85 | 86 | return template; 87 | }; 88 | 89 | ASQ.react.extend = function $$extend(name,build) { 90 | extensions[name] = build; 91 | 92 | return ASQ.react; 93 | }; 94 | 95 | })(); 96 | -------------------------------------------------------------------------------- /contrib/plugin.wrap.js: -------------------------------------------------------------------------------- 1 | // "wrap" 2 | ASQ.wrap = function $$wrap(fn,opts) { 3 | function checkThis(t,o) { 4 | return (!t || 5 | (typeof window != "undefined" && t === window) || 6 | (typeof global != "undefined" && t === global) 7 | ) ? o : t; 8 | } 9 | 10 | function paramSpread(gen) { 11 | return function *paramSpread(token) { 12 | yield *gen.apply(this,token.messages); 13 | }; 14 | } 15 | 16 | var errfcb, params_first, act, this_obj, spread_gen_params; 17 | 18 | opts = (opts && typeof opts == "object") ? opts : {}; 19 | 20 | if ( 21 | (opts.errfcb && opts.splitcb) || 22 | (opts.errfcb && opts.simplecb) || 23 | (opts.splitcb && opts.simplecb) || 24 | ("errfcb" in opts && !opts.errfcb && !opts.splitcb && !opts.simplecb) || 25 | (opts.params_first && opts.params_last) || 26 | (opts.spread && !opts.gen) 27 | ) { 28 | throw Error("Invalid options"); 29 | } 30 | 31 | // initialize default flags 32 | this_obj = (opts["this"] && typeof opts["this"] == "object") ? opts["this"] : ø; 33 | errfcb = opts.errfcb || !(opts.splitcb || opts.simplecb); 34 | params_first = !!opts.params_first || 35 | (!opts.params_last && !("params_first" in opts || opts.params_first)) || 36 | ("params_last" in opts && !opts.params_first && !opts.params_last) 37 | ; 38 | // spread (default: true) 39 | spread_gen_params = !!opts.spread || !("spread" in opts); 40 | 41 | if (params_first) { 42 | act = "push"; 43 | } 44 | else { 45 | act = "unshift"; 46 | } 47 | 48 | if (opts.gen) { 49 | if (spread_gen_params) { 50 | fn = paramSpread(fn); 51 | } 52 | return function $$wrapped$gen() { 53 | return ASQ(ASQ.messages.apply(ø,arguments)).runner(fn); 54 | }; 55 | } 56 | if (errfcb) { 57 | return function $$wrapped$errfcb() { 58 | var args = ARRAY_SLICE.call(arguments), 59 | _this = checkThis(this,this_obj) 60 | ; 61 | 62 | return ASQ(function $$asq(done){ 63 | args[act](done.errfcb); 64 | fn.apply(_this,args); 65 | }); 66 | }; 67 | } 68 | if (opts.splitcb) { 69 | return function $$wrapped$splitcb() { 70 | var args = ARRAY_SLICE.call(arguments), 71 | _this = checkThis(this,this_obj) 72 | ; 73 | 74 | return ASQ(function $$asq(done){ 75 | args[act](done,done.fail); 76 | fn.apply(_this,args); 77 | }); 78 | }; 79 | } 80 | if (opts.simplecb) { 81 | return function $$wrapped$simplecb() { 82 | var args = ARRAY_SLICE.call(arguments), 83 | _this = checkThis(this,this_obj) 84 | ; 85 | 86 | return ASQ(function $$asq(done){ 87 | args[act](done); 88 | fn.apply(_this,args); 89 | }); 90 | }; 91 | } 92 | }; 93 | -------------------------------------------------------------------------------- /contrib/EventEmitter.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * EventEmitter v4.2.7 - git.io/ee 3 | * Oliver Caldwell 4 | * MIT license 5 | * @preserve 6 | */ 7 | (function(){"use strict";function t(){}function r(t,n){for(var e=t.length;e--;)if(t[e].listener===n)return e;return-1}function n(e){return function(){return this[e].apply(this,arguments)}}var e=t.prototype,i=this,s=i.EventEmitter;e.getListeners=function(n){var r,e,t=this._getEvents();if(n instanceof RegExp){r={};for(e in t)t.hasOwnProperty(e)&&n.test(e)&&(r[e]=t[e])}else r=t[n]||(t[n]=[]);return r},e.flattenListeners=function(t){var e,n=[];for(e=0;e 0) { 101 | schedule(function $$schedule(){ 102 | api.or.apply(ø,or_queue); 103 | }); 104 | } 105 | } 106 | }); 107 | } 108 | return api; 109 | }; 110 | }); 111 | 112 | // "pCatch" 113 | ASQ.extend("pCatch",function $$extend(api,internals){ 114 | return function $$pcatch(failure) { 115 | if (internals("seq_aborted")) { 116 | return api; 117 | } 118 | 119 | api.pThen(void 0,failure); 120 | 121 | return api; 122 | }; 123 | }); 124 | -------------------------------------------------------------------------------- /legacy.js: -------------------------------------------------------------------------------- 1 | if (!Object.create) { 2 | Object.create = function(o) { 3 | function F(){} 4 | F.prototype = o; 5 | return new F(); 6 | }; 7 | } 8 | (function(){ 9 | try { 10 | Object.defineProperty({},"x",{}); 11 | } 12 | catch (err) { 13 | Object.defineProperty = function(obj,prop,desc) { 14 | obj[prop] = desc.value; 15 | return obj; 16 | }; 17 | } 18 | })(); 19 | if (!Object.keys) { 20 | Object.keys = function(obj){ 21 | var result = [], prop; 22 | for (prop in obj) { 23 | if (Object.prototype.hasOwnProperty.call(obj,prop)) { 24 | result.push(prop); 25 | } 26 | } 27 | return result; 28 | }; 29 | } 30 | // Adapted From: 31 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind 32 | if (!Function.prototype.bind) { 33 | Function.prototype.bind = function(oThis) { 34 | var aArgs = Array.prototype.slice.call(arguments,1), 35 | fToBind = this, 36 | fNOP = function(){}, 37 | fBound = function(){ 38 | return fToBind.apply( 39 | this instanceof fNOP && oThis ? this : oThis, 40 | aArgs.concat(Array.prototype.slice.call(arguments)) 41 | ); 42 | } 43 | ; 44 | fBound.prototype = Object.create(this.prototype); 45 | return fBound; 46 | }; 47 | } 48 | // Adapted From: 49 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray 50 | if (!Array.isArray) { 51 | Array.isArray = function(arg) { 52 | return Object.prototype.toString.call(arg) === "[object Array]"; 53 | }; 54 | } 55 | // Adapted From: 56 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach 57 | if (!Array.prototype.forEach) { 58 | Array.prototype.forEach = function(fn) { 59 | var t = Object(this); 60 | var len = t.length >>> 0; 61 | var thisArg = arguments.length >= 2 ? arguments[1] : void 0; 62 | for (var i=0; i>> 0; 75 | var thisArg = arguments.length >= 2 ? arguments[1] : void 0; 76 | for (var i=0; i>> 0; // Hack to convert object.length to a UInt32 89 | fromIndex = +fromIndex || 0; 90 | if (Math.abs(fromIndex) === Infinity) { 91 | fromIndex = 0; 92 | } 93 | if (fromIndex < 0) { 94 | fromIndex += length; 95 | if (fromIndex < 0) { 96 | fromIndex = 0; 97 | } 98 | } 99 | for (;fromIndex>> 0; 113 | var res = new Array(len); 114 | var thisArg = arguments.length >= 2 ? arguments[1] : void 0; 115 | for (var i = 0; i < len; i++) { 116 | if (i in t) { 117 | res[i] = fn.call(thisArg,t[i],i,t); 118 | } 119 | } 120 | return res; 121 | }; 122 | } 123 | -------------------------------------------------------------------------------- /contrib/plugin.iterable.js: -------------------------------------------------------------------------------- 1 | // "ASQ.iterable()" 2 | (function IIFE(){ 3 | var template; 4 | 5 | ASQ.iterable = function $$iterable() { 6 | function throwSequenceErrors() { 7 | throw (sequence_errors.length === 1 ? sequence_errors[0] : sequence_errors); 8 | } 9 | 10 | function notifyErrors() { 11 | var fn; 12 | 13 | seq_tick = null; 14 | 15 | if (seq_error) { 16 | if (or_queue.length === 0 && !error_reported) { 17 | error_reported = true; 18 | throwSequenceErrors(); 19 | } 20 | 21 | while (or_queue.length > 0) { 22 | error_reported = true; 23 | fn = or_queue.shift(); 24 | try { 25 | fn.apply(ø,sequence_errors); 26 | } 27 | catch (err) { 28 | if (checkBranding(err)) { 29 | sequence_errors = sequence_errors.concat(err); 30 | } 31 | else { 32 | sequence_errors.push(err); 33 | } 34 | if (or_queue.length === 0) { 35 | throwSequenceErrors(); 36 | } 37 | } 38 | } 39 | } 40 | } 41 | 42 | function val() { 43 | if (seq_error || seq_aborted || arguments.length === 0) { 44 | return sequence_api; 45 | } 46 | 47 | var args = ARRAY_SLICE.call(arguments).map(function mapper(arg){ 48 | if (typeof arg != "function") return function $$val() { return arg; }; 49 | else return arg; 50 | }); 51 | 52 | val_queue.push.apply(val_queue,args); 53 | 54 | return sequence_api; 55 | } 56 | 57 | function or() { 58 | if (seq_aborted || arguments.length === 0) { 59 | return sequence_api; 60 | } 61 | 62 | or_queue.push.apply(or_queue,arguments); 63 | 64 | if (!seq_tick) { 65 | seq_tick = schedule(notifyErrors); 66 | } 67 | 68 | return sequence_api; 69 | } 70 | 71 | function pipe() { 72 | if (seq_aborted || arguments.length === 0) { 73 | return sequence_api; 74 | } 75 | 76 | ARRAY_SLICE.call(arguments) 77 | .forEach(function $$each(fn){ 78 | val(fn).or(fn.fail); 79 | }); 80 | 81 | return sequence_api; 82 | } 83 | 84 | function next() { 85 | if (seq_error || seq_aborted || val_queue.length === 0) { 86 | if (val_queue.length > 0) { 87 | $throw$("Sequence cannot be iterated"); 88 | } 89 | return { done: true }; 90 | } 91 | 92 | try { 93 | return { value: val_queue.shift().apply(ø,arguments) }; 94 | } 95 | catch (err) { 96 | if (ASQ.isMessageWrapper(err)) { 97 | $throw$.apply(ø,err); 98 | } 99 | else { 100 | $throw$(err); 101 | } 102 | 103 | return {}; 104 | } 105 | } 106 | 107 | function $throw$() { 108 | if (seq_error || seq_aborted) { 109 | return sequence_api; 110 | } 111 | 112 | sequence_errors.push.apply(sequence_errors,arguments); 113 | seq_error = true; 114 | if (!seq_tick) { 115 | seq_tick = schedule(notifyErrors); 116 | } 117 | 118 | return sequence_api; 119 | } 120 | 121 | function $return$(val) { 122 | if (seq_error || seq_aborted) { 123 | val = void 0; 124 | } 125 | 126 | abort(); 127 | 128 | return { done: true, value: val }; 129 | } 130 | 131 | function abort() { 132 | if (seq_error || seq_aborted) { 133 | return; 134 | } 135 | 136 | seq_aborted = true; 137 | 138 | clearTimeout(seq_tick); 139 | seq_tick = null; 140 | val_queue.length = or_queue.length = sequence_errors.length = 0; 141 | } 142 | 143 | function duplicate() { 144 | var isq; 145 | 146 | template = { 147 | val_queue: val_queue.slice(), 148 | or_queue: or_queue.slice() 149 | }; 150 | isq = ASQ.iterable(); 151 | template = null; 152 | 153 | return isq; 154 | } 155 | 156 | // opt-out of global error reporting for this sequence 157 | function defer() { 158 | or_queue.push(function $$ignored(){}); 159 | return sequence_api; 160 | } 161 | 162 | // *********************************************** 163 | // Object branding utilities 164 | // *********************************************** 165 | function brandIt(obj) { 166 | Object.defineProperty(obj,brand,{ 167 | enumerable: false, 168 | value: true 169 | }); 170 | 171 | return obj; 172 | } 173 | 174 | var sequence_api, 175 | 176 | seq_error = false, 177 | error_reported = false, 178 | seq_aborted = false, 179 | 180 | seq_tick, 181 | 182 | val_queue = [], 183 | or_queue = [], 184 | 185 | sequence_errors = [] 186 | ; 187 | 188 | // *********************************************** 189 | // Setup the ASQ.iterable() public API 190 | // *********************************************** 191 | sequence_api = brandIt({ 192 | val: val, 193 | then: val, 194 | or: or, 195 | pipe: pipe, 196 | next: next, 197 | "throw": $throw$, 198 | "return": $return$, 199 | abort: abort, 200 | duplicate: duplicate, 201 | defer: defer 202 | }); 203 | 204 | // useful for ES6 `for..of` loops, 205 | // add `@@iterator` to simply hand back 206 | // our iterable sequence itself! 207 | sequence_api[(typeof Symbol == "function" && Symbol.iterator) || "@@iterator"] = function $$iter() { 208 | return sequence_api; 209 | }; 210 | 211 | // templating the iterable-sequence setup? 212 | if (template) { 213 | val_queue = template.val_queue.slice(0); 214 | or_queue = template.or_queue.slice(0); 215 | } 216 | 217 | // treat ASQ.iterable() constructor parameters as having been 218 | // passed to `val()` 219 | sequence_api.val.apply(ø,arguments); 220 | 221 | return sequence_api; 222 | }; 223 | 224 | })(); 225 | -------------------------------------------------------------------------------- /contrib/bundle.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var path = require("path"), 4 | fs = require("fs"), 5 | ugly = require("uglify-js"), 6 | args = require("minimist")( 7 | process.argv.slice(2), 8 | { 9 | "boolean": ["keep-es6"], 10 | "string": ["wrapper","bundle","min-bundle","exclude"] 11 | } 12 | ), 13 | testify = require("es-feature-tests/testify"), 14 | babel = require("babel-core"), 15 | 16 | bundle_name = "contrib.src.js", 17 | bundle_min_name = "contrib.js", 18 | bundle_wrapper_name = "contrib-wrapper.js", 19 | 20 | bundle_wrapper_code, 21 | bundle_str = "", 22 | which_plugins, 23 | exclude_plugins 24 | ; 25 | 26 | // ********************************************** 27 | 28 | // want help? 29 | if (args.help) { 30 | printHelp(); 31 | process.exit(1); 32 | } 33 | 34 | // pull in any override options 35 | if (args.bundle) { 36 | bundle_name = args.bundle; 37 | } 38 | if (args["min-bundle"]) { 39 | bundle_min_name = args["min-bundle"]; 40 | } 41 | if (args.wrapper) { 42 | bundle_wrapper_name = args.wrapper; 43 | } 44 | 45 | if (args._.length > 0) { 46 | which_plugins = args._.slice(); 47 | } 48 | if (args.exclude && args.exclude.length > 0) { 49 | exclude_plugins = Array.isArray(args.exclude) ? args.exclude.slice() : [args.exclude]; 50 | } 51 | 52 | bundlePlugins( 53 | /*dir=*/path.join(__dirname) 54 | ); 55 | 56 | bundle_wrapper_code = fs.readFileSync( 57 | path.join(__dirname,bundle_wrapper_name), 58 | { encoding: "utf8" } 59 | ); 60 | 61 | bundle_wrapper_code = bundle_wrapper_code.replace(/\/\*PLUGINS\*\//,function $$replace(){ return bundle_str; }); 62 | 63 | fs.writeFileSync( 64 | path.join(__dirname,bundle_name), 65 | bundle_wrapper_code, 66 | { encoding: "utf8" } 67 | ); 68 | 69 | console.log("Bundling complete."); 70 | 71 | if (!args["keep-es6"]) { 72 | console.log("Minifying to: " + bundle_min_name); 73 | 74 | try { 75 | result = ugly.minify(path.join(__dirname,bundle_name),{ 76 | mangle: { 77 | keep_fnames: true 78 | }, 79 | compress: { 80 | keep_fnames: true 81 | }, 82 | output: { 83 | comments: /^!/ 84 | } 85 | }); 86 | 87 | fs.writeFileSync( 88 | path.join(__dirname,bundle_min_name), 89 | result.code + "\n", 90 | { encoding: "utf8" } 91 | ); 92 | 93 | console.log("Complete."); 94 | } 95 | catch (err) { 96 | console.error(err); 97 | process.exit(1); 98 | } 99 | } 100 | 101 | function printHelp() { 102 | console.log("bundle.js usage:"); 103 | console.log(" bundle.js [ {OPTION} .. ] [ {PLUGIN-NAME} .. ]"); 104 | console.log(""); 105 | console.log("--help prints this help"); 106 | console.log("--wrapper=filename wrapper filename (\"contrib-wrapper.js\")"); 107 | console.log("--bundle=filename bundle filename (\"contrib.src.js\")"); 108 | console.log("--min-bundle=filename minified-bundle filename (\"contrib.js\")"); 109 | console.log("--exclude={PLUGIN-NAME} exclude a plugin from bundling"); 110 | console.log("--keep-es6={PLUGIN-NAME} no ES6 transpilation, skips minification"); 111 | console.log(""); 112 | console.log("If you don't pass any {PLUGIN-NAME} parameters, all available plugins"); 113 | console.log("(except any that are --exclude omitted) will be bundled."); 114 | console.log(""); 115 | console.log("If you pass one or more {PLUGIN-NAME} parameters, only the ones"); 116 | console.log("specified (except any that are --exclude omitted) will be bundled."); 117 | console.log(""); 118 | } 119 | 120 | function checkES6(text) { 121 | var tests_needed = testify.scan({ 122 | content: text 123 | }); 124 | 125 | if (tests_needed.length > 0) { 126 | text = babel.transform(text,{ 127 | ast: false, 128 | compact: false 129 | }).code; 130 | } 131 | 132 | return text; 133 | } 134 | 135 | // bundle the contrib plugins 136 | function bundlePlugins(dir) { 137 | var files = fs.readdirSync(dir); 138 | 139 | files.sort(); 140 | 141 | files.forEach(function $$each(file){ 142 | var st = fs.statSync(path.join(dir,file)), 143 | contents, collection_id, 144 | plugin_name = file.replace(/plugin\.(.*)\.js/,"$1") 145 | ; 146 | 147 | // template file to read contents and compile? 148 | if (st.isFile() && /^plugin\..*\.js$/.test(file) && 149 | ( 150 | !which_plugins || 151 | ~which_plugins.indexOf(plugin_name) 152 | ) 153 | ) { 154 | if (!exclude_plugins || 155 | !~exclude_plugins.indexOf(plugin_name) 156 | ) { 157 | console.log("Including plugin: " + plugin_name); 158 | 159 | contents = fs.readFileSync(path.join(dir,file),{ encoding: "utf8" }); 160 | if (!args["keep-es6"]) { 161 | contents = checkES6(contents); 162 | } 163 | 164 | bundle_str += contents; 165 | } 166 | else { 167 | console.log(" (excluding): " + plugin_name); 168 | } 169 | 170 | // remove plugin from specified list 171 | if (which_plugins && ~which_plugins.indexOf(plugin_name)) { 172 | which_plugins.splice(which_plugins.indexOf(plugin_name),1); 173 | } 174 | if (exclude_plugins && ~exclude_plugins.indexOf(plugin_name)) { 175 | exclude_plugins.splice(exclude_plugins.indexOf(plugin_name),1); 176 | } 177 | } 178 | else if (st.isDirectory() && !/node_modules/.test(file)) { 179 | bundlePlugins(path.join(dir,file)); 180 | } 181 | }); 182 | 183 | if (which_plugins && which_plugins.length > 0) { 184 | console.error("** Warning ** Requested plugins not found: " + which_plugins.join(" ")); 185 | } 186 | if (exclude_plugins && exclude_plugins.length > 0) { 187 | console.error("** Warning ** Excluded plugins not found: " + exclude_plugins.join(" ")); 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /contrib/plugin.runner.js: -------------------------------------------------------------------------------- 1 | // "runner" 2 | ASQ.extend("runner",function $$extend(api,internals){ 3 | 4 | return function $$runner() { 5 | if (internals("seq_error") || internals("seq_aborted") || 6 | arguments.length === 0 7 | ) { 8 | return api; 9 | } 10 | 11 | var args = ARRAY_SLICE.call(arguments); 12 | 13 | api 14 | .then(function $$then(mainDone){ 15 | 16 | function wrap(v) { 17 | // function? expected to produce an iterator 18 | // (like a generator) or a promise 19 | if (typeof v === "function") { 20 | // call function passing in the control token 21 | // note: neutralize `this` in call to prevent 22 | // unexpected behavior 23 | v = v.call(ø,token); 24 | 25 | // promise returned (ie, from async function)? 26 | if (isPromise(v)) { 27 | // wrap it in iterable sequence 28 | v = ASQ.iterable(v); 29 | } 30 | } 31 | // an iterable sequence? duplicate it (in case of multiple runs) 32 | else if (ASQ.isSequence(v) && "next" in v) { 33 | v = v.duplicate(); 34 | } 35 | // wrap anything else in iterable sequence 36 | else { 37 | v = ASQ.iterable(v); 38 | } 39 | 40 | // a sequence to tap for errors? 41 | if (ASQ.isSequence(v)) { 42 | // listen for any sequence failures 43 | v.or(function $$or(){ 44 | // signal iteration-error 45 | mainDone.fail.apply(ø,arguments); 46 | }); 47 | } 48 | 49 | return v; 50 | } 51 | 52 | function addWrapped() { 53 | iterators.push.apply( 54 | iterators, 55 | ARRAY_SLICE.call(arguments).map(wrap) 56 | ); 57 | } 58 | 59 | function iterateOrQuit(iterFn,now) { 60 | // still have some co-routine runs to process? 61 | if (iterators.length > 0) { 62 | if (now) iterFn(); 63 | else schedule(iterFn); 64 | } 65 | // all done! 66 | else { 67 | // previous value message? 68 | if (typeof next_val !== "undefined") { 69 | // not a message wrapper array? 70 | if (!ASQ.isMessageWrapper(next_val)) { 71 | // wrap value for the subsequent `apply(..)` 72 | next_val = [next_val]; 73 | } 74 | } 75 | else { 76 | // nothing to affirmatively pass along 77 | next_val = []; 78 | } 79 | 80 | // signal done with all co-routine runs 81 | mainDone.apply(ø,next_val); 82 | } 83 | } 84 | 85 | var iterators = args, 86 | token = { 87 | messages: ARRAY_SLICE.call(arguments,1), 88 | add: addWrapped 89 | }, 90 | iter, iter_action, ret, next_val = token 91 | ; 92 | 93 | // map co-routines to round-robin list of iterators 94 | iterators = iterators.map(wrap); 95 | 96 | // async iteration of round-robin list 97 | (function iterate(throwNext){ 98 | iter_action = throwNext ? "throw" : "next"; 99 | 100 | // get next co-routine in list 101 | iter = iterators.shift(); 102 | 103 | // process the iteration 104 | try { 105 | // multiple messages to send to an iterable 106 | // sequence? 107 | if (ASQ.isMessageWrapper(next_val) && 108 | ASQ.isSequence(iter) 109 | ) { 110 | ret = iter[iter_action].apply(iter,next_val); 111 | } 112 | else { 113 | ret = iter[iter_action](next_val); 114 | } 115 | } 116 | catch (err) { 117 | return mainDone.fail(err); 118 | } 119 | 120 | // bail on run in aborted sequence 121 | if (internals("seq_aborted")) return; 122 | 123 | // was the control token yielded? 124 | if (ret.value === token) { 125 | // round-robin: put co-routine back into the list 126 | // at the end where it was so it can be processed 127 | // again on next loop-iteration 128 | if (!ret.done) { 129 | iterators.push(iter); 130 | } 131 | next_val = token; 132 | iterateOrQuit(iterate,/*now=*/false); 133 | } 134 | else { 135 | // not a recognized ASQ instance returned? 136 | if (!ASQ.isSequence(ret.value)) { 137 | // received a thenable/promise back? 138 | if (isPromise(ret.value)) { 139 | // wrap in a sequence 140 | ret.value = ASQ().promise(ret.value); 141 | } 142 | // thunk yielded? 143 | else if (typeof ret.value === "function") { 144 | // wrap thunk call in a sequence 145 | var fn = ret.value; 146 | ret.value = ASQ(function $$ASQ(done){ 147 | fn(done.errfcb); 148 | }); 149 | } 150 | // message wrapper returned? 151 | else if (ASQ.isMessageWrapper(ret.value)) { 152 | // wrap message(s) in a sequence 153 | ret.value = ASQ.apply(ø, 154 | // don't let `apply(..)` discard an empty message 155 | // wrapper! instead, pass it along as its own value 156 | // itself. 157 | ret.value.length > 0 ? ret.value : ASQ.messages(undefined) 158 | ); 159 | } 160 | // non-undefined value returned? 161 | else if (typeof ret.value !== "undefined") { 162 | // wrap the value in a sequence 163 | ret.value = ASQ(ret.value); 164 | } 165 | else { 166 | // make an empty sequence 167 | ret.value = ASQ(); 168 | } 169 | } 170 | 171 | ret.value 172 | .val(function $$val(){ 173 | // bail on run in aborted sequence 174 | if (internals("seq_aborted")) return; 175 | 176 | if (arguments.length > 0) { 177 | // save any return messages for input 178 | // to next iteration 179 | next_val = arguments.length > 1 ? 180 | ASQ.messages.apply(ø,arguments) : 181 | arguments[0] 182 | ; 183 | } 184 | 185 | // still more to iterate? 186 | if (!ret.done) { 187 | // was the control token passed along? 188 | if (next_val === token) { 189 | // round-robin: put co-routine back into the list 190 | // at the end, so that the the next iterator can be 191 | // processed on next loop-iteration 192 | iterators.push(iter); 193 | } 194 | else { 195 | // put co-routine back in where it just 196 | // was so it can be processed again on 197 | // next loop-iteration 198 | iterators.unshift(iter); 199 | } 200 | } 201 | 202 | iterateOrQuit(iterate,/*now=*/true); 203 | }) 204 | .or(function $$or(){ 205 | // bail on run in aborted sequence 206 | if (internals("seq_aborted")) return; 207 | 208 | if (!ret.done) { 209 | // put co-routine back in where it just 210 | // was so it can be processed again on 211 | // next loop-iteration 212 | iterators.unshift(iter); 213 | 214 | next_val = arguments.length > 1 ? 215 | ASQ.messages.apply(ø,arguments) : 216 | arguments[0]; 217 | 218 | iterate(/*throwNext=*/true); 219 | } 220 | else { 221 | // if an error is left over but iteration now 222 | // complete, pass out to the main sequence 223 | mainDone.fail.apply(ø,arguments); 224 | } 225 | }); 226 | } 227 | })(); 228 | }); 229 | 230 | return api; 231 | }; 232 | }); 233 | -------------------------------------------------------------------------------- /contrib/plugin.reactHelpers.js: -------------------------------------------------------------------------------- 1 | // "react" helpers 2 | (function IIFE(){ 3 | 4 | var Ar = ASQ.react; 5 | 6 | Ar.of = function $$react$of() { 7 | function reactor(next) { 8 | if (!started) { 9 | started = true; 10 | if (args.length > 0) { 11 | args.shift().val(function val(){ 12 | next.apply(ø,arguments); 13 | if (args.length > 0) { 14 | args.shift().val(val); 15 | } 16 | }); 17 | } 18 | } 19 | } 20 | 21 | var started, args = ARRAY_SLICE.call(arguments) 22 | .map(function wrapper(arg){ 23 | if (!ASQ.isSequence(arg)) arg = ASQ(arg); 24 | return arg; 25 | }); 26 | 27 | return Ar(reactor); 28 | }; 29 | 30 | Ar.all = Ar.zip = makeReactOperator(/*buffer=*/true); 31 | Ar.allLatest = makeReactOperator(/*buffer=false*/); 32 | Ar.latest = Ar.combineLatest = makeReactOperator(/*buffer=*/false,/*keep=*/true); 33 | 34 | Ar.any = Ar.merge = function $$react$any(){ 35 | function reactor(next,registerTeardown){ 36 | function processSequence(def){ 37 | function trigger(){ 38 | var args = ASQ.messages.apply(ø,arguments); 39 | // still observing sequence-streams? 40 | if (seqs && seqs.length > 0) { 41 | // fire off reactive sequence instance 42 | next.apply(ø,args); 43 | } 44 | // keep sequence going 45 | return args; 46 | } 47 | 48 | // sequence-stream event listener 49 | def.seq.val(trigger); 50 | } 51 | 52 | // observe all sequence-streams 53 | seqs.forEach(processSequence); 54 | 55 | // listen for stop() of reactive sequence 56 | registerTeardown(function $$teardown(){ 57 | seqs = null; 58 | }); 59 | } 60 | 61 | // observe all sequence-streams 62 | var seqs = tapSequences.apply(null,arguments); 63 | 64 | if (seqs.length == 0) return; 65 | 66 | return Ar(reactor); 67 | }; 68 | 69 | Ar.distinct = function $$react$distinct(seq){ 70 | return Ar.filter(seq,makeDistinctFilterer(/*keepAll=*/true)); 71 | }; 72 | 73 | Ar.distinctConsecutive = Ar.distinctUntilChanged = function $$react$distinct$consecutive(seq) { 74 | return Ar.filter(seq,makeDistinctFilterer(/*keepAll=*/false)); 75 | }; 76 | 77 | Ar.filter = function $$react$filter(seq,filterer){ 78 | function reactor(next,registerTeardown) { 79 | function trigger(){ 80 | var messages = ASQ.messages.apply(ø,arguments); 81 | 82 | if (filterer && filterer.apply(ø,messages)) { 83 | // fire off reactive sequence instance 84 | next.apply(ø,messages); 85 | } 86 | 87 | // keep sequence going 88 | return messages; 89 | } 90 | 91 | // sequence-stream event listener 92 | def.seq.val(trigger); 93 | 94 | // listen for stop() of reactive sequence 95 | registerTeardown(function $$teardown(){ 96 | def = filterer = null; 97 | }); 98 | } 99 | 100 | // observe sequence-stream 101 | var def = tapSequences(seq)[0]; 102 | 103 | if (!def) return; 104 | 105 | return Ar(reactor); 106 | }; 107 | 108 | Ar.fromObservable = function $$react$from$observable(obsv){ 109 | function reactor(next,registerTeardown){ 110 | // process buffer (if any) 111 | buffer.forEach(next); 112 | buffer.length = 0; 113 | 114 | // start non-buffered notifications? 115 | if (!buffer.complete) { 116 | notify = next; 117 | } 118 | 119 | registerTeardown(function $$teardown(){ 120 | obsv.dispose(); 121 | }); 122 | } 123 | 124 | function notify(v) { 125 | buffer.push(v); 126 | } 127 | 128 | var buffer = []; 129 | 130 | obsv.subscribe( 131 | function $$on$next(v){ 132 | notify(v); 133 | }, 134 | function $$on$error(){}, 135 | function $$on$complete(){ 136 | buffer.complete = true; 137 | obsv.dispose(); 138 | } 139 | ); 140 | 141 | return Ar(reactor); 142 | }; 143 | 144 | ASQ.extend("toObservable",function $$extend(api,internals){ 145 | return function $$to$observable(){ 146 | function init(observer) { 147 | function define(pair){ 148 | function listen(){ 149 | var args = ASQ.messages.apply(ø,arguments); 150 | observer[pair[1]].apply(observer, 151 | args.length == 1 ? [args[0]] : args 152 | ); 153 | return args; 154 | } 155 | 156 | api[pair[0]](listen); 157 | } 158 | 159 | [["val","onNext"],["or","onError"]] 160 | .forEach(define); 161 | } 162 | 163 | return Rx.Observable.create(init); 164 | }; 165 | }); 166 | 167 | function tapSequences() { 168 | function tapSequence(seq) { 169 | // temporary `trigger` which, if called before being replaced 170 | // below, creates replacement proxy sequence with the 171 | // event message(s) re-fired 172 | function trigger() { 173 | var args = ARRAY_SLICE.call(arguments); 174 | def.seq = Ar(function $$react(next){ 175 | next.apply(ø,args); 176 | }); 177 | } 178 | 179 | if (ASQ.isSequence(seq)) { 180 | var def = { seq: seq }; 181 | 182 | // listen for events from the sequence-stream 183 | seq.val(function $$val(){ 184 | trigger.apply(ø,arguments); 185 | return ASQ.messages.apply(ø,arguments); 186 | }); 187 | 188 | // make a reactive sequence to act as a proxy to the original 189 | // sequence 190 | def.seq = Ar(function $$react(next){ 191 | // replace the temporary trigger (created above) 192 | // with this proxy's trigger 193 | trigger = next; 194 | }); 195 | 196 | return def; 197 | } 198 | } 199 | 200 | return ARRAY_SLICE.call(arguments) 201 | .map(tapSequence) 202 | .filter(Boolean); 203 | } 204 | 205 | function makeReactOperator(buffer,keep) { 206 | return function $$react$operator(){ 207 | function reactor(next,registerTeardown){ 208 | function processSequence(def) { 209 | // sequence-stream event listener 210 | function trigger() { 211 | var args = ASQ.messages.apply(ø,arguments); 212 | // still observing sequence-streams? 213 | if (seqs && seqs.length > 0) { 214 | // store event message(s), if any 215 | seq_events[seq_id] = 216 | (buffer ? seq_events[seq_id] : []).concat( 217 | args.length > 0 ? (args.length > 1 ? [args] : args[0]) : undefined 218 | ); 219 | 220 | // collect event message(s) across the 221 | // sequence-stream sources 222 | var messages = seq_events.reduce(function reducer(msgs,eventList,idx){ 223 | if (eventList.length > 0) msgs.push(eventList[0]); 224 | return msgs; 225 | },[]); 226 | 227 | // did all sequence-streams get an event? 228 | if (messages.length == seq_events.length) { 229 | if (messages.length == 1) messages = messages[0]; 230 | 231 | // fire off reactive sequence instance 232 | next.apply(ø,messages); 233 | 234 | // discard stored event message(s)? 235 | if (!keep) { 236 | seq_events.forEach(function $$each(eventList){ 237 | eventList.shift(); 238 | }); 239 | } 240 | } 241 | } 242 | // keep sequence going 243 | return args; 244 | } 245 | 246 | var seq_id = seq_events.length; 247 | seq_events.push([]); 248 | def.seq.val(trigger); 249 | } 250 | 251 | // process all sequence-streams 252 | seqs.forEach(processSequence); 253 | 254 | // listen for stop() of reactive sequence 255 | registerTeardown(function $$teardown(){ 256 | seqs = seq_events = null; 257 | }); 258 | } 259 | 260 | var seq_events = [], 261 | // observe all sequence-streams 262 | seqs = tapSequences.apply(null,arguments) 263 | ; 264 | 265 | if (seqs.length == 0) return; 266 | 267 | return Ar(reactor); 268 | }; 269 | } 270 | 271 | function makeDistinctFilterer(keepAll) { 272 | function filterer() { 273 | function isDuplicate(msgSet) { 274 | return ( 275 | msgSet.length == message_set.length && 276 | msgSet.every(function $$every(val,idx){ 277 | return val === message_set[idx]; 278 | }) 279 | ); 280 | } 281 | 282 | var message_set = ASQ.messages.apply(ø,arguments); 283 | 284 | // any messages in message-set to check against? 285 | if (message_set.length > 0) { 286 | // duplicate message-set? 287 | if (msg_sets.some(isDuplicate)) { 288 | return false; 289 | } 290 | 291 | // remember all message-sets for future distinct checking? 292 | if (keepAll) { 293 | msg_sets.push(message_set); 294 | } 295 | // only keep the last message-set for distinct-consecutive 296 | // checking 297 | else { 298 | msg_sets[0] = message_set; 299 | } 300 | } 301 | 302 | // allow distinct non-duplicate value through 303 | return true; 304 | } 305 | 306 | var msg_sets = []; 307 | 308 | return filterer; 309 | } 310 | 311 | })(); 312 | -------------------------------------------------------------------------------- /contrib/plugin.goCSP.js: -------------------------------------------------------------------------------- 1 | // "go-style CSP" 2 | (function IIFE(){ 3 | 4 | // filter out already-resolved queue entries 5 | function filterResolved(queue) { 6 | return queue.filter(function $$filter(entry){ 7 | return !entry.resolved; 8 | }); 9 | } 10 | 11 | function closeQueue(queue,finalValue) { 12 | queue.forEach(function $$each(iter){ 13 | if (!iter.resolved) { 14 | iter.next(); 15 | iter.next(finalValue); 16 | } 17 | }); 18 | queue.length = 0; 19 | } 20 | 21 | function channel(bufSize) { 22 | var ch = { 23 | close: function $$close(){ 24 | ch.closed = true; 25 | closeQueue(ch.put_queue,false); 26 | closeQueue(ch.take_queue,ASQ.csp.CLOSED); 27 | }, 28 | closed: false, 29 | messages: [], 30 | put_queue: [], 31 | take_queue: [], 32 | buffer_size: +bufSize || 0 33 | }; 34 | return ch; 35 | } 36 | 37 | function unblock(iter) { 38 | if (iter && !iter.resolved) { 39 | iter.next(iter.next().value); 40 | } 41 | } 42 | 43 | function put(channel,value) { 44 | var ret; 45 | 46 | if (channel.closed) { 47 | return false; 48 | } 49 | 50 | // remove already-resolved entries 51 | channel.put_queue = filterResolved(channel.put_queue); 52 | channel.take_queue = filterResolved(channel.take_queue); 53 | 54 | // immediate put? 55 | if (channel.messages.length < channel.buffer_size) { 56 | channel.messages.push(value); 57 | unblock(channel.take_queue.shift()); 58 | return true; 59 | } 60 | // queued put 61 | else { 62 | channel.put_queue.push( 63 | // make a notifiable iterable for 'put' blocking 64 | ASQ.iterable() 65 | .then(function $$then(){ 66 | if (!channel.closed) { 67 | channel.messages.push(value); 68 | return true; 69 | } 70 | else { 71 | return false; 72 | } 73 | }) 74 | ); 75 | 76 | // wrap a sequence/promise around the iterable 77 | ret = ASQ( 78 | channel.put_queue[channel.put_queue.length - 1] 79 | ); 80 | 81 | // take waiting on this queued put? 82 | if (channel.take_queue.length > 0) { 83 | unblock(channel.put_queue.shift()); 84 | unblock(channel.take_queue.shift()); 85 | } 86 | 87 | return ret; 88 | } 89 | } 90 | 91 | function putAsync(channel,value,cb) { 92 | var ret = ASQ(put(channel,value)); 93 | 94 | if (cb && typeof cb == "function") { 95 | ret.val(cb); 96 | } 97 | else { 98 | return ret; 99 | } 100 | } 101 | 102 | function take(channel) { 103 | var ret; 104 | 105 | try { 106 | ret = takem(channel); 107 | } 108 | catch (err) { 109 | ret = err; 110 | } 111 | 112 | if (ASQ.isSequence(ret)) { 113 | ret.pCatch(function $$pcatch(err){ 114 | return err; 115 | }); 116 | } 117 | 118 | return ret; 119 | } 120 | 121 | function takeAsync(channel,cb) { 122 | var ret = ASQ(take(channel)); 123 | 124 | if (cb && typeof cb == "function") { 125 | ret.val(cb); 126 | } 127 | else { 128 | return ret; 129 | } 130 | } 131 | 132 | function takem(channel) { 133 | var msg; 134 | 135 | if (channel.closed) { 136 | return ASQ.csp.CLOSED; 137 | } 138 | 139 | // remove already-resolved entries 140 | channel.put_queue = filterResolved(channel.put_queue); 141 | channel.take_queue = filterResolved(channel.take_queue); 142 | 143 | // immediate take? 144 | if (channel.messages.length > 0) { 145 | msg = channel.messages.shift(); 146 | unblock(channel.put_queue.shift()); 147 | if (msg instanceof Error) { 148 | throw msg; 149 | } 150 | return msg; 151 | } 152 | // queued take 153 | else { 154 | channel.take_queue.push( 155 | // make a notifiable iterable for 'take' blocking 156 | ASQ.iterable() 157 | .then(function $$then(){ 158 | if (!channel.closed) { 159 | var v = channel.messages.shift(); 160 | if (v instanceof Error) { 161 | throw v; 162 | } 163 | return v; 164 | } 165 | else { 166 | return ASQ.csp.CLOSED; 167 | } 168 | }) 169 | ); 170 | 171 | // wrap a sequence/promise around the iterable 172 | msg = ASQ( 173 | channel.take_queue[channel.take_queue.length - 1] 174 | ); 175 | 176 | // put waiting on this take? 177 | if (channel.put_queue.length > 0) { 178 | unblock(channel.put_queue.shift()); 179 | unblock(channel.take_queue.shift()); 180 | } 181 | 182 | return msg; 183 | } 184 | } 185 | 186 | function takemAsync(channel,cb) { 187 | var ret = ASQ(takem(channel)); 188 | 189 | if (cb && typeof cb == "function") { 190 | ret.pThen(cb,cb); 191 | } 192 | else { 193 | return ret.val(function $$val(v){ 194 | if (v instanceof Error) { 195 | throw v; 196 | } 197 | return v; 198 | }); 199 | } 200 | } 201 | 202 | function alts(actions) { 203 | var closed, open, handlers, i, isq, ret, resolved = false; 204 | 205 | // used `alts(..)` incorrectly? 206 | if (!Array.isArray(actions) || actions.length == 0) { 207 | throw Error("Invalid usage"); 208 | } 209 | 210 | closed = []; 211 | open = []; 212 | handlers = []; 213 | 214 | // separate actions by open/closed channel status 215 | actions.forEach(function $$each(action){ 216 | var channel = Array.isArray(action) ? action[0] : action; 217 | 218 | // remove already-resolved entries 219 | channel.put_queue = filterResolved(channel.put_queue); 220 | channel.take_queue = filterResolved(channel.take_queue); 221 | 222 | if (channel.closed) { 223 | closed.push(channel); 224 | } 225 | else { 226 | open.push(action); 227 | } 228 | }); 229 | 230 | // if no channels are still open, we're done 231 | if (open.length == 0) { 232 | return { value: ASQ.csp.CLOSED, channel: closed }; 233 | } 234 | 235 | // can any channel action be executed immediately? 236 | for (i=0; i 0) { 246 | return { value: take(open[i]), channel: open[i] }; 247 | } 248 | } 249 | 250 | isq = ASQ.iterable(); 251 | var ret = ASQ(isq); 252 | 253 | // setup channel action handlers 254 | for (i=0; i 0) { 289 | schedule(function handleUnblocking(){ 290 | if (!resolved) { 291 | unblock(channel.put_queue.shift()); 292 | unblock(channel.take_queue.shift()); 293 | } 294 | },0); 295 | } 296 | } 297 | // take action? 298 | else { 299 | channel = action; 300 | 301 | // define take handler 302 | handlers.push( 303 | ASQ.iterable() 304 | .then(function $$then(){ 305 | resolved = true; 306 | 307 | // mark all handlers across this `alts(..)` as resolved now 308 | handlers = handlers.filter(function $$filter(handler){ 309 | return !(handler.resolved = true); 310 | }); 311 | 312 | // channel still open? 313 | if (!channel.closed) { 314 | isq.next({ value: channel.messages.shift(), channel: channel }); 315 | } 316 | // channel already closed? 317 | else { 318 | isq.next({ value: ASQ.csp.CLOSED, channel: channel }); 319 | } 320 | }) 321 | ); 322 | 323 | // queue up take handler 324 | channel.take_queue.push(handlers[handlers.length-1]); 325 | 326 | // put waiting on this queued take? 327 | if (channel.put_queue.length > 0) { 328 | schedule(function handleUnblocking(){ 329 | if (!resolved) { 330 | unblock(channel.put_queue.shift()); 331 | unblock(channel.take_queue.shift()); 332 | } 333 | }); 334 | } 335 | } 336 | })(open[i]); 337 | } 338 | 339 | return ret; 340 | } 341 | 342 | function altsAsync(chans,cb) { 343 | var ret = ASQ(alts(chans)); 344 | 345 | if (cb && typeof cb == "function") { 346 | ret.pThen(cb,cb); 347 | } 348 | else { 349 | return ret; 350 | } 351 | } 352 | 353 | function timeout(delay) { 354 | var ch = channel(); 355 | setTimeout(ch.close,delay); 356 | return ch; 357 | } 358 | 359 | function go(gen,args) { 360 | // goroutine arguments passed? 361 | if (arguments.length > 1) { 362 | if (!args || !Array.isArray(args)) { 363 | args = [args]; 364 | } 365 | } 366 | else { 367 | args = []; 368 | } 369 | 370 | return function *$$go(token) { 371 | 372 | // unblock the overall goroutine handling 373 | function unblock() { 374 | token.unblock_count++; 375 | 376 | if (token.block && !token.block.marked) { 377 | token.block.marked = true; 378 | token.block.next(); 379 | } 380 | } 381 | 382 | var ret, msg, err, type, done = false, it; 383 | 384 | // keep track of how many requests for unblocking 385 | // have occurred 386 | token.unblock_count = (token.unblock_count || 0); 387 | 388 | // keep track of how many goroutines are running 389 | // so we can infer when we're done go'ing 390 | token.go_count = (token.go_count || 0) + 1; 391 | 392 | // need to initialize a set of goroutines? 393 | if (token.go_count === 1) { 394 | // create a default channel for these goroutines 395 | token.channel = channel(); 396 | token.channel.messages = token.messages; 397 | token.channel.go = function $$go(){ 398 | // add the goroutine (called with any args) to 399 | // the handling queue 400 | token.add( go.apply(ø,arguments) ); 401 | // unblock the goroutine handling for this 402 | // new goroutine 403 | unblock(); 404 | }; 405 | // starting out with initial channel messages? 406 | if (token.channel.messages.length > 0) { 407 | // fake back-pressure blocking for each 408 | token.channel.put_queue = token.channel.messages.map(function $$map(){ 409 | // make a notifiable iterable for 'put' blocking 410 | return ASQ.iterable() 411 | .then(function $$then(){ 412 | unblock(token.channel.take_queue.shift()); 413 | return !token.channel.closed; 414 | }); 415 | }); 416 | } 417 | } 418 | 419 | // initialize the generator 420 | it = gen.apply(ø,[token.channel].concat(args)); 421 | 422 | (function iterate(){ 423 | 424 | function next() { 425 | // keep going with next step in goroutine? 426 | if (!done) { 427 | iterate(); 428 | } 429 | // unblock overall goroutine handling to 430 | // continue with other goroutines 431 | else { 432 | unblock(); 433 | } 434 | } 435 | 436 | // has a resumption value been achieved yet? 437 | if (!ret) { 438 | // try to resume the goroutine 439 | try { 440 | // resume with injected exception? 441 | if (err) { 442 | ret = it.throw(err); 443 | err = null; 444 | } 445 | // resume normally 446 | else { 447 | ret = it.next(msg); 448 | } 449 | } 450 | // resumption failed, so bail 451 | catch (e) { 452 | done = true; 453 | err = e; 454 | msg = null; 455 | unblock(); 456 | return; 457 | } 458 | 459 | // keep track of the result of the resumption 460 | done = ret.done; 461 | ret = ret.value; 462 | type = typeof ret; 463 | 464 | // if this goroutine is complete, unblock the 465 | // overall goroutine handling 466 | if (done) { 467 | unblock(); 468 | } 469 | 470 | // received a thenable/promise back? 471 | if (isPromise(ret)) { 472 | ret = ASQ().promise(ret); 473 | } 474 | 475 | // wait for the value? 476 | if (ASQ.isSequence(ret)) { 477 | ret.val(function $$val(){ 478 | ret = null; 479 | msg = arguments.length > 1 ? 480 | ASQ.messages.apply(ø,arguments) : 481 | arguments[0] 482 | ; 483 | next(); 484 | }) 485 | .or(function $$or(){ 486 | ret = null; 487 | msg = arguments.length > 1 ? 488 | ASQ.messages.apply(ø,arguments) : 489 | arguments[0] 490 | ; 491 | if (msg instanceof Error) { 492 | err = msg; 493 | msg = null; 494 | } 495 | next(); 496 | }); 497 | } 498 | // immediate value, prepare it to go right back in 499 | else { 500 | msg = ret; 501 | ret = null; 502 | next(); 503 | } 504 | } 505 | })(); 506 | 507 | // keep this goroutine alive until completion 508 | while (!done) { 509 | // transfer control to another goroutine 510 | yield token; 511 | 512 | // need to block overall goroutine handling 513 | // while idle? 514 | if (!done && !token.block && token.unblock_count === 0) { 515 | // wait here while idle 516 | yield (token.block = ASQ.iterable()); 517 | 518 | token.block = false; 519 | } 520 | 521 | if (token.unblock_count > 0) token.unblock_count--; 522 | } 523 | 524 | // this goroutine is done now 525 | token.go_count--; 526 | 527 | // all goroutines done? 528 | if (token.go_count === 0) { 529 | // any lingering blocking need to be cleaned up? 530 | unblock(); 531 | 532 | // capture any untaken messages 533 | msg = ASQ.messages.apply(ø,token.messages); 534 | 535 | // need to implicitly force-close channel? 536 | if (token.channel && !token.channel.closed) { 537 | token.channel.closed = true; 538 | token.channel.put_queue.length = token.channel.take_queue.length = 0; 539 | token.channel.close = token.channel.go = token.channel.messages = null; 540 | } 541 | token.channel = null; 542 | } 543 | 544 | // make sure leftover error or message are 545 | // passed along 546 | if (err) { 547 | throw err; 548 | } 549 | else if (token.go_count === 0) { 550 | return msg; 551 | } 552 | else { 553 | return token; 554 | } 555 | }; 556 | } 557 | 558 | ASQ.csp = { 559 | chan: channel, 560 | put: put, 561 | putAsync: putAsync, 562 | take: take, 563 | takeAsync: takeAsync, 564 | takem: takem, 565 | takemAsync: takemAsync, 566 | alts: alts, 567 | altsAsync: altsAsync, 568 | timeout: timeout, 569 | go: go, 570 | CLOSED: {} 571 | }; 572 | 573 | })(); 574 | -------------------------------------------------------------------------------- /asq.src.js: -------------------------------------------------------------------------------- 1 | /*! asynquence 2 | v0.10.3 (c) Kyle Simpson 3 | MIT License: http://getify.mit-license.org 4 | */ 5 | 6 | (function UMD(name,context,definition){ 7 | if (typeof define === "function" && define.amd) { define(definition); } 8 | else if (typeof module !== "undefined" && module.exports) { module.exports = definition(); } 9 | else { context[name] = definition(name,context); } 10 | })("ASQ",this,function DEF(name,context){ 11 | "use strict"; 12 | 13 | var cycle, scheduling_queue, 14 | timer = (typeof setImmediate !== "undefined") ? 15 | function $$timer(fn) { return setImmediate(fn); } : 16 | setTimeout 17 | ; 18 | 19 | // Note: using a queue instead of array for efficiency 20 | function Queue() { 21 | var first, last, item; 22 | 23 | function Item(fn) { 24 | this.fn = fn; 25 | this.next = void 0; 26 | } 27 | 28 | return { 29 | add: function $$add(fn) { 30 | item = new Item(fn); 31 | if (last) { 32 | last.next = item; 33 | } 34 | else { 35 | first = item; 36 | } 37 | last = item; 38 | item = void 0; 39 | }, 40 | drain: function $$drain() { 41 | var f = first; 42 | first = last = cycle = null; 43 | 44 | while (f) { 45 | f.fn(); 46 | f = f.next; 47 | } 48 | } 49 | }; 50 | } 51 | 52 | scheduling_queue = Queue(); 53 | 54 | function schedule(fn) { 55 | scheduling_queue.add(fn); 56 | if (!cycle) { 57 | cycle = timer(scheduling_queue.drain); 58 | } 59 | } 60 | 61 | function tapSequence(def) { 62 | // temporary `trigger` which, if called before being replaced 63 | // above, creates replacement proxy sequence with the 64 | // success/error message(s) pre-injected 65 | function trigger() { 66 | def.seq = createSequence.apply(ø,arguments).defer(); 67 | } 68 | 69 | // fail trigger 70 | trigger.fail = function $$trigger$fail() { 71 | var args = ARRAY_SLICE.call(arguments); 72 | def.seq = createSequence(function $$create$sequence(done){ 73 | done.fail.apply(ø,args); 74 | }) 75 | .defer(); 76 | }; 77 | 78 | // listen for signals from the sequence 79 | def.seq 80 | // note: cannot use `seq.pipe(trigger)` because we 81 | // need to be able to update the shared closure 82 | // to change `trigger` 83 | .val(function $$val(){ 84 | trigger.apply(ø,arguments); 85 | return ASQmessages.apply(ø,arguments); 86 | }) 87 | .or(function $$or(){ 88 | trigger.fail.apply(ø,arguments); 89 | }); 90 | 91 | // make a sequence to act as a proxy to the original 92 | // sequence 93 | def.seq = createSequence(function $$create$sequence(done){ 94 | // replace the temporary trigger (created below) 95 | // with this proxy's trigger 96 | trigger = done; 97 | }) 98 | .defer(); 99 | } 100 | 101 | function createSequence() { 102 | 103 | function scheduleSequenceTick() { 104 | if (seq_aborted) { 105 | sequenceTick(); 106 | } 107 | else if (!seq_tick) { 108 | seq_tick = schedule(sequenceTick); 109 | } 110 | } 111 | 112 | function throwSequenceErrors() { 113 | throw (sequence_errors.length === 1 ? sequence_errors[0] : sequence_errors); 114 | } 115 | 116 | function sequenceTick() { 117 | var fn, args; 118 | 119 | seq_tick = null; 120 | // remove the temporary `unpause()` hook, if any 121 | delete sequence_api.unpause; 122 | 123 | if (seq_aborted) { 124 | clearTimeout(seq_tick); 125 | seq_tick = null; 126 | then_queue.length = or_queue.length = sequence_messages.length = sequence_errors.length = 0; 127 | } 128 | else if (seq_error) { 129 | if (or_queue.length === 0 && !error_reported) { 130 | error_reported = true; 131 | throwSequenceErrors(); 132 | } 133 | 134 | while (or_queue.length) { 135 | error_reported = true; 136 | fn = or_queue.shift(); 137 | try { 138 | fn.apply(ø,sequence_errors); 139 | } 140 | catch (err) { 141 | if (isMessageWrapper(err)) { 142 | sequence_errors = sequence_errors.concat(err); 143 | } 144 | else { 145 | sequence_errors.push(err); 146 | if (err.stack) { sequence_errors.push(err.stack); } 147 | } 148 | if (or_queue.length === 0) { 149 | throwSequenceErrors(); 150 | } 151 | } 152 | } 153 | } 154 | else if (then_ready && then_queue.length > 0) { 155 | then_ready = false; 156 | fn = then_queue.shift(); 157 | args = sequence_messages.slice(); 158 | sequence_messages.length = 0; 159 | args.unshift(createStepCompletion()); 160 | 161 | try { 162 | fn.apply(ø,args); 163 | } 164 | catch (err) { 165 | if (isMessageWrapper(err)) { 166 | sequence_errors = sequence_errors.concat(err); 167 | } 168 | else { 169 | sequence_errors.push(err); 170 | } 171 | seq_error = true; 172 | scheduleSequenceTick(); 173 | } 174 | } 175 | } 176 | 177 | function createStepCompletion() { 178 | 179 | function done() { 180 | // ignore this call? 181 | if (seq_error || seq_aborted || then_ready || step_completed) { 182 | return; 183 | } 184 | 185 | step_completed = true; 186 | then_ready = true; 187 | sequence_messages.push.apply(sequence_messages,arguments); 188 | sequence_errors.length = 0; 189 | 190 | scheduleSequenceTick(); 191 | } 192 | 193 | done.fail = function $$step$fail(){ 194 | // ignore this call? 195 | if (seq_error || seq_aborted || then_ready || step_completed) { 196 | return; 197 | } 198 | 199 | seq_error = true; 200 | sequence_messages.length = 0; 201 | sequence_errors.push.apply(sequence_errors,arguments); 202 | 203 | scheduleSequenceTick(); 204 | }; 205 | 206 | done.abort = function $$step$abort(){ 207 | if (seq_error || seq_aborted) { 208 | return; 209 | } 210 | 211 | then_ready = false; 212 | seq_aborted = true; 213 | sequence_messages.length = sequence_errors.length = 0; 214 | 215 | scheduleSequenceTick(); 216 | }; 217 | 218 | // handles "error-first" (aka "node-style") callbacks 219 | done.errfcb = function $$step$errfcb(err){ 220 | if (err) { 221 | done.fail(err); 222 | } 223 | else { 224 | done.apply(ø,ARRAY_SLICE.call(arguments,1)); 225 | } 226 | }; 227 | 228 | var step_completed = false; 229 | 230 | return done; 231 | } 232 | 233 | function createGate(stepCompletion,segments,seqMessages) { 234 | 235 | function resetGate() { 236 | clearTimeout(gate_tick); 237 | gate_tick = segment_completion = 238 | segment_messages = segment_error_message = null; 239 | } 240 | 241 | function scheduleGateTick() { 242 | if (gate_aborted) { 243 | return gateTick(); 244 | } 245 | 246 | if (!gate_tick) { 247 | gate_tick = schedule(gateTick); 248 | } 249 | } 250 | 251 | function gateTick() { 252 | if (seq_error || seq_aborted || gate_completed) { 253 | return; 254 | } 255 | 256 | var msgs = []; 257 | 258 | gate_tick = null; 259 | 260 | if (gate_error) { 261 | stepCompletion.fail.apply(ø,segment_error_message); 262 | 263 | resetGate(); 264 | } 265 | else if (gate_aborted) { 266 | stepCompletion.abort(); 267 | 268 | resetGate(); 269 | } 270 | else if (checkGate()) { 271 | gate_completed = true; 272 | 273 | // collect all the messages from the gate segments 274 | segment_completion 275 | .forEach(function $$each(sc,i){ 276 | msgs.push(segment_messages["s" + i]); 277 | }); 278 | 279 | stepCompletion.apply(ø,msgs); 280 | 281 | resetGate(); 282 | } 283 | } 284 | 285 | function checkGate() { 286 | if (segment_completion.length === 0) { 287 | return; 288 | } 289 | 290 | var fulfilled = true; 291 | 292 | segment_completion.some(function $$some(segcom){ 293 | if (segcom === null) { 294 | fulfilled = false; 295 | return true; // break 296 | } 297 | }); 298 | 299 | return fulfilled; 300 | } 301 | 302 | function createSegmentCompletion() { 303 | 304 | function done() { 305 | // ignore this call? 306 | if (seq_error || seq_aborted || gate_error || 307 | gate_aborted || gate_completed || 308 | segment_completion[segment_completion_idx] 309 | ) { 310 | return; 311 | } 312 | 313 | // put gate-segment messages into `messages`-branded 314 | // container 315 | var args = ASQmessages.apply(ø,arguments); 316 | 317 | segment_messages["s" + segment_completion_idx] = 318 | args.length > 1 ? args : args[0]; 319 | segment_completion[segment_completion_idx] = true; 320 | 321 | scheduleGateTick(); 322 | } 323 | 324 | var segment_completion_idx = segment_completion.length; 325 | 326 | done.fail = function $$segment$fail(){ 327 | // ignore this call? 328 | if (seq_error || seq_aborted || gate_error || 329 | gate_aborted || gate_completed || 330 | segment_completion[segment_completion_idx] 331 | ) { 332 | return; 333 | } 334 | 335 | gate_error = true; 336 | segment_error_message = ARRAY_SLICE.call(arguments); 337 | 338 | scheduleGateTick(); 339 | }; 340 | 341 | done.abort = function $$segment$abort(){ 342 | if (seq_error || seq_aborted || gate_error || 343 | gate_aborted || gate_completed 344 | ) { 345 | return; 346 | } 347 | 348 | gate_aborted = true; 349 | 350 | // abort() is an immediate/synchronous action 351 | gateTick(); 352 | }; 353 | 354 | // handles "error-first" (aka "node-style") callbacks 355 | done.errfcb = function $$segment$errfcb(err){ 356 | if (err) { 357 | done.fail(err); 358 | } 359 | else { 360 | done.apply(ø,ARRAY_SLICE.call(arguments,1)); 361 | } 362 | }; 363 | 364 | // placeholder for when a gate-segment completes 365 | segment_completion[segment_completion_idx] = null; 366 | 367 | return done; 368 | } 369 | 370 | var gate_error = false, 371 | gate_aborted = false, 372 | gate_completed = false, 373 | 374 | args, 375 | err_msg, 376 | 377 | segment_completion = [], 378 | segment_messages = {}, 379 | segment_error_message, 380 | 381 | gate_tick 382 | ; 383 | 384 | segments.some(function $$some(seg){ 385 | if (gate_error || gate_aborted) { 386 | return true; // break 387 | } 388 | 389 | args = seqMessages.slice(); 390 | args.unshift(createSegmentCompletion()); 391 | try { 392 | seg.apply(ø,args); 393 | } 394 | catch (err) { 395 | err_msg = err; 396 | gate_error = true; 397 | return true; // break 398 | } 399 | }); 400 | 401 | if (err_msg) { 402 | if (isMessageWrapper(err_msg)) { 403 | stepCompletion.fail.apply(ø,err_msg); 404 | } 405 | else { 406 | stepCompletion.fail(err_msg); 407 | } 408 | } 409 | } 410 | 411 | function then() { 412 | if (seq_error || seq_aborted || arguments.length === 0) { 413 | return sequence_api; 414 | } 415 | 416 | wrapArgs(arguments,thenWrapper) 417 | .forEach(function $$each(v){ 418 | if (isSequence(v)) { 419 | seq(v); 420 | } 421 | else { 422 | then_queue.push(v); 423 | } 424 | }); 425 | 426 | scheduleSequenceTick(); 427 | 428 | return sequence_api; 429 | } 430 | 431 | function or() { 432 | if (seq_aborted || arguments.length === 0) { 433 | return sequence_api; 434 | } 435 | 436 | or_queue.push.apply(or_queue,arguments); 437 | 438 | scheduleSequenceTick(); 439 | 440 | return sequence_api; 441 | } 442 | 443 | function gate() { 444 | if (seq_error || seq_aborted || arguments.length === 0) { 445 | return sequence_api; 446 | } 447 | 448 | var fns = ARRAY_SLICE.call(arguments) 449 | // map any sequences to gate segments 450 | .map(function $$map(v){ 451 | var def; 452 | 453 | // is `v` a sequence or iterable-sequence? 454 | if (isSequence(v)) { 455 | def = { seq: v }; 456 | tapSequence(def); 457 | return function $$segment(done) { 458 | def.seq.pipe(done); 459 | }; 460 | } 461 | else return v; 462 | }); 463 | 464 | then(function $$then(done){ 465 | var args = ARRAY_SLICE.call(arguments,1); 466 | createGate(done,fns,args); 467 | }); 468 | 469 | return sequence_api; 470 | } 471 | 472 | function pipe() { 473 | if (seq_aborted || arguments.length === 0) { 474 | return sequence_api; 475 | } 476 | 477 | ARRAY_SLICE.call(arguments) 478 | .forEach(function $$each(trigger){ 479 | then(function $$then(done){ 480 | trigger.apply(ø,ARRAY_SLICE.call(arguments,1)); 481 | done(); 482 | }) 483 | .or(trigger.fail); 484 | }); 485 | 486 | return sequence_api; 487 | } 488 | 489 | function seq() { 490 | if (seq_error || seq_aborted || arguments.length === 0) { 491 | return sequence_api; 492 | } 493 | 494 | ARRAY_SLICE.call(arguments) 495 | .forEach(function $$each(v){ 496 | var def = { seq: v }; 497 | 498 | // is `fn` a sequence or iterable-sequence? 499 | if (isSequence(v)) { 500 | tapSequence(def); 501 | } 502 | 503 | then(function $$then(done){ 504 | var _v = def.seq; 505 | // check if this argument is not already a sequence? 506 | // if not, assume a function to invoke that will return 507 | // a sequence. 508 | if (!isSequence(_v)) { 509 | _v = def.seq.apply(ø,ARRAY_SLICE.call(arguments,1)); 510 | } 511 | // pipe the provided sequence into our current sequence 512 | _v.pipe(done); 513 | }); 514 | }); 515 | 516 | return sequence_api; 517 | } 518 | 519 | function val() { 520 | if (seq_error || seq_aborted || arguments.length === 0) { 521 | return sequence_api; 522 | } 523 | 524 | ARRAY_SLICE.call( 525 | wrapArgs(arguments,valWrapper) 526 | ) 527 | .forEach(function $$each(fn){ 528 | then(function $$then(done){ 529 | var msgs = fn.apply(ø,ARRAY_SLICE.call(arguments,1)); 530 | if (!isMessageWrapper(msgs)) { 531 | msgs = ASQmessages(msgs); 532 | } 533 | done.apply(ø,msgs); 534 | }); 535 | }); 536 | 537 | return sequence_api; 538 | } 539 | 540 | function promise() { 541 | function wrap(fn) { 542 | return function $$fn(){ 543 | fn.apply(ø,isMessageWrapper(arguments[0]) ? arguments[0] : arguments); 544 | }; 545 | } 546 | 547 | if (seq_error || seq_aborted || arguments.length === 0) { 548 | return sequence_api; 549 | } 550 | 551 | ARRAY_SLICE.call(arguments) 552 | .forEach(function $$each(pr){ 553 | then(function $$then(done){ 554 | var _pr = pr; 555 | // check if this argument is a non-thenable function, and 556 | // if so, assume we shold invoke it to return a promise 557 | // NOTE: `then` duck-typing of promises is stupid. 558 | if (typeof pr === "function" && typeof pr.then !== "function") { 559 | _pr = pr.apply(ø,ARRAY_SLICE.call(arguments,1)); 560 | } 561 | // now, hook up the promise to the sequence 562 | _pr.then( 563 | wrap(done), 564 | wrap(done.fail) 565 | ); 566 | }); 567 | }); 568 | 569 | return sequence_api; 570 | } 571 | 572 | function fork() { 573 | var trigger; 574 | 575 | // listen for success at this point in the sequence 576 | val(function $$val(){ 577 | if (trigger) { 578 | trigger.apply(ø,arguments); 579 | } 580 | else { 581 | trigger = createSequence.apply(ø,arguments).defer(); 582 | } 583 | return ASQmessages.apply(ø,arguments); 584 | }); 585 | // listen for error at this point in the sequence 586 | or(function $$or(){ 587 | if (trigger) { 588 | trigger.fail.apply(ø,arguments); 589 | } 590 | else { 591 | var args = ARRAY_SLICE.call(arguments); 592 | trigger = createSequence().then(function $$then(done){ 593 | done.fail.apply(ø,args); 594 | }) 595 | .defer(); 596 | } 597 | }); 598 | 599 | // create the forked sequence which will receive 600 | // the success/error stream from the main sequence 601 | return createSequence() 602 | .then(function $$then(done){ 603 | if (!trigger) { 604 | trigger = done; 605 | } 606 | else { 607 | trigger.pipe(done); 608 | } 609 | }) 610 | .defer(); 611 | } 612 | 613 | function abort() { 614 | if (seq_error) { 615 | return sequence_api; 616 | } 617 | 618 | seq_aborted = true; 619 | 620 | sequenceTick(); 621 | 622 | return sequence_api; 623 | } 624 | 625 | function duplicate() { 626 | var sq; 627 | 628 | template = { 629 | then_queue: then_queue.slice(), 630 | or_queue: or_queue.slice() 631 | }; 632 | sq = createSequence(); 633 | template = null; 634 | 635 | return sq; 636 | } 637 | 638 | function unpause() { 639 | sequence_messages.push.apply(sequence_messages,arguments); 640 | if (seq_tick === true) seq_tick = null; 641 | scheduleSequenceTick(); 642 | } 643 | 644 | // opt-out of global error reporting for this sequence 645 | function defer() { 646 | or_queue.push(function ignored(){}); 647 | return sequence_api; 648 | } 649 | 650 | function internals(name,value) { 651 | var set = (arguments.length > 1); 652 | switch (name) { 653 | case "seq_error": 654 | if (set) { seq_error = value; } 655 | else { return seq_error; } 656 | break; 657 | case "seq_aborted": 658 | if (set) { seq_aborted = value; } 659 | else { return seq_aborted; } 660 | break; 661 | case "then_ready": 662 | if (set) { then_ready = value; } 663 | else { return then_ready; } 664 | break; 665 | case "then_queue": 666 | return then_queue; 667 | case "or_queue": 668 | return or_queue; 669 | case "sequence_messages": 670 | return sequence_messages; 671 | case "sequence_errors": 672 | return sequence_errors; 673 | } 674 | } 675 | 676 | function includeExtensions() { 677 | Object.keys(extensions) 678 | .forEach(function $$each(name){ 679 | sequence_api[name] = sequence_api[name] || 680 | extensions[name](sequence_api,internals); 681 | }); 682 | } 683 | 684 | var seq_error = false, 685 | error_reported = false, 686 | seq_aborted = false, 687 | then_ready = true, 688 | 689 | then_queue = [], 690 | or_queue = [], 691 | 692 | sequence_messages = [], 693 | sequence_errors = [], 694 | 695 | seq_tick, 696 | 697 | // brand the sequence API so we can detect ASQ instances 698 | sequence_api = brandIt({ 699 | then: then, 700 | or: or, 701 | // alias of `or(..)` to `onerror(..)` 702 | onerror: or, 703 | gate: gate, 704 | // alias of `gate(..)` to `all(..)` for symmetry 705 | // with native ES6 promises 706 | all: gate, 707 | pipe: pipe, 708 | seq: seq, 709 | val: val, 710 | promise: promise, 711 | fork: fork, 712 | abort: abort, 713 | duplicate: duplicate, 714 | defer: defer 715 | }) 716 | ; 717 | 718 | // include any extensions 719 | includeExtensions(); 720 | 721 | // templating the sequence setup? 722 | if (template) { 723 | then_queue = template.then_queue.slice(); 724 | or_queue = template.or_queue.slice(); 725 | 726 | // templating a sequence starts it out paused 727 | // add temporary `unpause()` API hook 728 | sequence_api.unpause = unpause; 729 | seq_tick = true; 730 | } 731 | 732 | // treat ASQ() constructor parameters as having been 733 | // passed to `then()` 734 | sequence_api.then.apply(ø,arguments); 735 | 736 | return sequence_api; 737 | } 738 | 739 | 740 | // *********************************************** 741 | // Object branding utilities 742 | // *********************************************** 743 | function brandIt(obj) { 744 | return Object.defineProperty(obj,brand,{ 745 | enumerable: false, 746 | value: true 747 | }); 748 | } 749 | 750 | function checkBranding(val) { 751 | return !!(val != null && typeof val === "object" && val[brand]); 752 | } 753 | 754 | 755 | // *********************************************** 756 | // Value messages utilities 757 | // *********************************************** 758 | // wrapper helpers 759 | function valWrapper(numArgs) { 760 | // `numArgs` indicates how many pre-bound arguments 761 | // will be sent in. 762 | return ASQmessages.apply(ø, 763 | // pass along only the pre-bound arguments 764 | ARRAY_SLICE.call(arguments).slice(1,numArgs+1) 765 | ); 766 | } 767 | 768 | function thenWrapper(numArgs) { 769 | // Because of bind() partial-application, will 770 | // receive pre-bound arguments before the `done()`, 771 | // rather than it being first as usual. 772 | // `numArgs` indicates how many pre-bound arguments 773 | // will be sent in. 774 | arguments[numArgs+1] // the `done()` 775 | .apply(ø, 776 | // pass along only the pre-bound arguments 777 | ARRAY_SLICE.call(arguments).slice(1,numArgs+1) 778 | ); 779 | } 780 | 781 | function wrapArgs(args,wrapper) { 782 | var i, j; 783 | args = ARRAY_SLICE.call(args); 784 | for (i=0; i 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | ``` 254 | 255 | ### Plugin Extensions 256 | 257 | `ASQ.extend( {name}, {build} )` allows you to specify an API extension, giving it a `name` and a `build` function callback that should return the implementation of your API extension. The `build` callback is provided two parameters, the sequence `api` instance, and an `internals(..)` method, which lets you get or set values of various internal properties (generally, don't use this if you can avoid it). 258 | 259 | Example: 260 | 261 | ```js 262 | // "foobar" plugin, which injects message "foobar!" 263 | // into the sequence stream 264 | ASQ.extend("foobar",function __build__(api,internals){ 265 | return function __foobar__() { 266 | api.val(function __val__(){ 267 | return "foobar!"; 268 | }); 269 | 270 | return api; 271 | }; 272 | }); 273 | 274 | ASQ() 275 | .foobar() // our custom plugin! 276 | .val(function(msg){ 277 | console.log(msg); // foobar! 278 | }); 279 | ``` 280 | 281 | The `/contrib/` directory includes a variety of [optional contrib plugins](https://github.com/getify/asynquence/blob/master/contrib/README.md) as helpers for async flow-controls. See these plugins for more complex examples of how to extend the *asynquence* API. 282 | 283 | 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 package: `asynquence-contrib`. 284 | 285 | There are also other bundle options included with the npm package, such as `contrib-es6.src.js` and `contrib-common.js`. See [Building Contrib Bundle](https://github.com/getify/asynquence/blob/master/contrib/README.md#building-contrib-bundle) for more information. 286 | 287 | #### Iterable Sequences 288 | 289 | 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 static function 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. 290 | 291 | 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**. 292 | 293 | 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 message(s) to the current step in the sequence. `next(..)` always returns an *iterator result* object like: 294 | 295 | ```js 296 | { 297 | value: ... // return messages 298 | done: true|false // sequence iteration complete? 299 | } 300 | ``` 301 | 302 | **Note:** If the `value` property is absent, it's assumed to be `undefined`, and if the `done` property is absent, it's assumed to be `false`. 303 | 304 | `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. 305 | 306 | 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. 307 | 308 | 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. You can also call `return(..)` (just like on normal iterators), which `abort()`s the sequence and returns an *iterator result* with the value passed in, if any, and `done: true`. 309 | 310 | Iterable sequences are a special subset of sequences, and as such, some of the normal *asynquence* API variations do not exist, such as `gate(..)`, `seq(..)`, and `promise(..)`. 311 | 312 | ```js 313 | function step(num) { 314 | return "Step " + num; 315 | } 316 | 317 | var sq = ASQ.iterable() 318 | .then(step) 319 | .then(step) 320 | .then(step); 321 | 322 | for (var i=0, ret; 323 | (ret = sq.next(i+1)) && !ret.done; 324 | i++ 325 | ) { 326 | console.log(ret.value); 327 | } 328 | // Step 1 329 | // Step 2 330 | // Step 3 331 | ``` 332 | 333 | This example shows sync iteration with a `for` loop, but of course, `next(..)` can be called in various [async ways to iterate](https://gist.github.com/getify/8211148#file-ex2-async-iteration-js) the sequence over time. 334 | 335 | Iterable sequence steps can either be a function that produces a value, or a direct (non-function) value itself: 336 | 337 | ```js 338 | var sq = ASQ.iterable() 339 | .then(42) 340 | .then(function(x){ 341 | return x * 2; 342 | }) 343 | .then("hello world"); 344 | 345 | sq.next(); // { value: 42 } 346 | sq.next(5); // { value: 10 } 347 | sq.next(); // { value: "hello world" } 348 | sq.next(); // { done: true } 349 | ``` 350 | 351 | 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. 352 | 353 | ### Multiple parameters 354 | 355 | API methods take one or more functions as their parameters: 356 | 357 | * `gate(..)` treats multiple functions as segments in the same gate. 358 | * 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 programmatically), but since they take multiple parameters, you can use `.apply(..)` to spread an array of values out. 359 | 360 | ### Promises/A+ Compliance 361 | 362 | **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.** 363 | 364 | ----- 365 | 366 | If you're looking for actual Promises/A+ compliance, I've just released [Native Promise Only](http://github.com/getify/native-promise-only), a tiny and fast polyfill of purely just the native ES6 `Promise()` mechanism. 367 | 368 | ----- 369 | 370 | *asynquence* is intentionally designed to hide/abstract the idea and use of Promises, such that you can do quick and easy async flow-control programming without some of the hassles/tedium of creating `Promise`s directly. 371 | 372 | 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 hidden promises behave predictably like standard Promises where they need to, so *asynquence* as an abstraction offers the same trust guarantees. 373 | 374 | If you are also using other Promises implementations alongside *asynquence*, you *can* quite easily receive and consume a regular Promise value (or thenable) from some other method into the signal/control flow for an *asynquence* sequence. 375 | 376 | For example, if using jQuery, the [Q promises library](https://github.com/kriskowal/q), and *asynquence*: 377 | 378 | ```js 379 | // Using *Q*, make a standard Promise out 380 | // of jQuery's Ajax (non-standard) "promise" 381 | var p = Q( $.ajax(..) ); 382 | 383 | // Now, asynquence flow-control including a 384 | // standard Promise 385 | ASQ() 386 | .then(function(done){ 387 | setTimeout(done,100); 388 | }) 389 | // subsume a standard Promise into the sequence 390 | .promise(p) 391 | .val(function(ajaxResp){ 392 | console.log(ajaxResp); 393 | }); 394 | ``` 395 | 396 | **Despite API similarities** (like the presence of `then(..)` on the API), an *asynquence* instance is **not itself** designed to be used *as a Promise value* linked/passed to another standard Promise or other utilities that expect real promises. 397 | 398 | 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. 399 | 400 | **However,** if you really need a standard native `Promise` from your sequence, you can use the [`toPromise` contrib plugin](https://github.com/getify/asynquence/blob/master/contrib/README.md#topromise-plugin), which vends/forks an actual native `Promise` off an *asynquence* sequence instance. 401 | 402 | ## Browser, node.js (CommonJS), AMD: ready! 403 | 404 | The *asynquence* library is packaged with a light variation of the [UMD (universal module definition)](https://github.com/umdjs/umd) pattern, which means the same file is suitable for inclusion either as a normal browser .js file, as a node.js module, or as an AMD module. Can't get any simpler than that, can it? 405 | 406 | For browser usage, simply include the `asq.js` library file. For node.js usage, install the `asynquence` package via npm, then `require(..)` the module: 407 | 408 | ```js 409 | var ASQ = require("asynquence"); 410 | ``` 411 | 412 | **Note:** The `ASQ.noConflict()` static function 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. 413 | 414 | ## Usage Examples 415 | 416 | Using the following example setup: 417 | 418 | ```js 419 | function fn1(done) { 420 | alert("Step 1"); 421 | setTimeout(done,1000); 422 | } 423 | 424 | function fn2(done) { 425 | alert("Step 2"); 426 | setTimeout(done,1000); 427 | } 428 | 429 | function yay() { 430 | alert("Done!"); 431 | } 432 | ``` 433 | 434 | Execute `fn1`, then `fn2`, then finally `yay`: 435 | 436 | ```js 437 | ASQ(fn1) 438 | .then(fn2) 439 | .then(yay); 440 | ``` 441 | 442 | Pass messages from step to step: 443 | 444 | ```js 445 | ASQ(function(done){ 446 | setTimeout(function(){ 447 | done("hello"); 448 | },1000); 449 | }) 450 | .then(function(done,msg1){ 451 | setTimeout(function(){ 452 | done(msg1,"world"); 453 | },1000); 454 | }) 455 | .then(function(_,msg1,msg2){ // basically ignoring this step's completion trigger (`_`) 456 | alert("Greeting: " + msg1 + " " + msg2); 457 | // 'Greeting: hello world' 458 | }); 459 | ``` 460 | 461 | Handle step failure: 462 | 463 | ```js 464 | ASQ(function(done){ 465 | setTimeout(function(){ 466 | done("hello"); 467 | },1000); 468 | }) 469 | .then(function(done,msg1){ 470 | setTimeout(function(){ 471 | // note the `fail` flag here!! 472 | done.fail(msg1,"world"); 473 | },1000); 474 | }) 475 | .then(function(){ 476 | // sequence fails, won't ever get called 477 | }) 478 | .or(function(msg1,msg2){ 479 | alert("Failure: " + msg1 + " " + msg2); 480 | // 'Failure: hello world' 481 | }); 482 | ``` 483 | 484 | Create a step that's a parallel gate: 485 | 486 | ```js 487 | ASQ() 488 | // normal async step 489 | .then(function(done){ 490 | setTimeout(function(){ 491 | done("hello"); 492 | },1000); 493 | }) 494 | // parallel gate step (segments run in parallel) 495 | .gate( 496 | function(done,greeting){ // gate segment 497 | setTimeout(function(){ 498 | // 2 gate messages! 499 | done(greeting,"world"); 500 | },500); 501 | }, 502 | function(done,greeting){ // gate segment 503 | setTimeout(function(){ 504 | // only 1 gate message! 505 | done(greeting + " mikey"); 506 | },100); 507 | // this segment finishes first, but message 508 | // still kept "in order" 509 | } 510 | ) 511 | .then(function(_,msg1,msg2){ 512 | // msg1 is an array of the 2 gate messages 513 | // from the first segment 514 | // msg2 is the single message (not an array) 515 | // from the second segment 516 | 517 | alert("Greeting: " + msg1[0] + " " + msg1[1]); 518 | // 'Greeting: hello world' 519 | alert("Greeting: " + msg2); 520 | // 'Greeting: hello mikey' 521 | }); 522 | ``` 523 | 524 | Use `pipe(..)`, `seq(..)`, and `val(..)` helpers: 525 | 526 | ```js 527 | var seq = ASQ() 528 | .then(function(done){ 529 | ASQ() 530 | .then(function(done){ 531 | setTimeout(function(){ 532 | done("Hello World"); 533 | },100); 534 | }) 535 | .pipe(done); // pipe sequence output to `done` completion trigger 536 | }) 537 | .val(function(msg){ // NOTE: no completion trigger passed in! 538 | return msg.toUpperCase(); // map return value as step output 539 | }) 540 | .seq(function(msg){ // NOTE: no completion trigger passed in! 541 | var seq = ASQ(); 542 | 543 | seq 544 | .then(function(done){ 545 | setTimeout(function(){ 546 | done(msg.split(" ")[0]); 547 | },100); 548 | }); 549 | 550 | return seq; // pipe this sub-sequence back into the main sequence 551 | }) 552 | .then(function(_,msg){ 553 | alert(msg); // "HELLO" 554 | }); 555 | ``` 556 | 557 | Abort a sequence in progress: 558 | 559 | ```js 560 | var seq = ASQ() 561 | .then(fn1) 562 | .then(fn2) 563 | .then(yay); 564 | 565 | setTimeout(function(){ 566 | // will stop the sequence before running 567 | // steps `fn2` and `yay` 568 | seq.abort(); 569 | },100); 570 | 571 | // same as above 572 | ASQ() 573 | .then(fn1) 574 | .then(function(done){ 575 | setTimeout(function(){ 576 | // `abort` flag will stop the sequence 577 | // before running steps `fn2` and `yay` 578 | done.abort(); 579 | },100); 580 | }) 581 | .then(fn2) 582 | .then(yay); 583 | ``` 584 | 585 | ## Builds 586 | 587 | The core library file can be built (minified) with an included utility: 588 | 589 | ``` 590 | ./build-core.js 591 | ``` 592 | 593 | However, the recommended way to invoke this utility is via npm: 594 | 595 | ``` 596 | npm run-script build-core 597 | ``` 598 | 599 | ## License 600 | 601 | The code and all the documentation are released under the MIT license. 602 | 603 | http://getify.mit-license.org/ 604 | -------------------------------------------------------------------------------- /contrib/README.md: -------------------------------------------------------------------------------- 1 | # asynquence Contrib 2 | 3 | Optional *asynquence* plugin helpers. 4 | 5 | ## Function-wrapping Adapter 6 | 7 | To integrate *asynquence* into standard callback-oriented code bases, sometimes it's preferable to create wrappers around commonly used callback-oriented functions, to be used in place of the original functions. The wrapper automatically constructs an *asynquence* instance when called, and wires up the underlying call to the original callback-oriented function so that it maps its output behavior to the *asynquence* instance. 8 | 9 | For example, we can call `ASQ.wrap(..)` to wrap `fs.readFile(..)` in Node.js, suppressing the callback in its signature and turning it into an *asynquence*-returning function: 10 | 11 | ```js 12 | var readfile = ASQ.wrap(fs.readFile); 13 | 14 | readfile("something.txt",{ encoding: "utf8" }) 15 | .val(function(contents){ 16 | // file contents 17 | }) 18 | .or(function(err){ 19 | // oops, `err` in reading file! 20 | }); 21 | ``` 22 | 23 | **Note:** `ASQ.wrap(..)` creates a function which will automatically be async in nature, even if the underlying function would normally have called its callback immediately/synchronously. **DO NOT RELY** on ordered side-effects of such wrapped functions. 24 | 25 | Most of Node.js's standard functions expect an "error-first" style callback, and they also expect it to be at the end of the arguments list (aka "parameters first"). The default settings for `ASQ.wrap(..)` assume that sort of function signature. 26 | 27 | However, you may need to use *asynquence* with other sorts of function signatures. 28 | 29 | For example, some functions are the opposite in parameter order (aka "parameters last"), where the callback must be the first argument and any other parameters are passed after it. You can pass an options-object as the second parameter to `ASQ.wrap(..)` to signal alternative function signature behavior: 30 | 31 | ```js 32 | function doSomething(cb,p1,p2) { 33 | // do something with `p1` and `p2`, then later 34 | // call `cb` as an error-first cb 35 | } 36 | 37 | var better = ASQ.wrap(doSomething,{ params_last: true }); 38 | 39 | better("val 1","val 2") 40 | .val(function(result){ 41 | // result 42 | }) 43 | .or(function(err){ 44 | // oops, `err` occurred! 45 | }); 46 | ``` 47 | 48 | You also may want to specify a specific `this` binding to use with the underlying function/method call. You can do such with the normal JS `.bind(..)` utility, like `ASQ.wrap( fn.bind(obj) )`, but that gives you permanent *hard-binding* that can't be overriden, which may or may not be suitable. 49 | 50 | If you want the more flexible "soft binding" (an alternate default `this` binding -- instead of `window` / `global` -- that can still be overriden), you can specify a `this` in the options object, like so: 51 | 52 | ```js 53 | function doSomething(cb) { 54 | cb(this.id); 55 | } 56 | 57 | var o1 = { id: 42 }; 58 | var o2 = { id: "foobar" }; 59 | 60 | var better = ASQ.wrap(doSomething,{ this: o1 }); 61 | 62 | // use `o1` as default soft-bound `this` 63 | better() 64 | .val(function(id){ 65 | id; // 42 66 | }); 67 | 68 | // `this` is still overridable 69 | better.call(o2) 70 | .val(function(id){ 71 | id; // "foobar" 72 | }); 73 | ``` 74 | 75 | The complete list of options you can pass: 76 | 77 | * `this`: (default: `{ }`) specifies a *soft-binding* (aka, alternate default) for `this` for the underlying function call being wrapped 78 | * `params_first`: (default: `true`) indicates "parameters first" style signature 79 | * `params_last`: (default: `false`) indicates "parameters last" style signature 80 | * `errfcb`: (default: `true`) indicates "error-first" style callback expected 81 | * `splitcb`: (default: `false`) indicates split success and error callbacks expected 82 | * `simplecb`: (default: `false`) indicates simple (success-only) callback expected, which assumes an error is either passed opaquely (inaccessible to *asynquence* handling) to the callback in some way (which you must handle), or an error is `throw`n to be `try..catch` caught (which *asynquence* will handle) 83 | * `gen`: (default: `false`) indicates that you've passed in a generator (ES6) to wrap (see below). 84 | * `spread`: (default: `true`) only in effect for `gen: true`, indicates that `token.messages` should be spread out as arguments to the generator instead of the normal `token` being passed. 85 | 86 | Obviously, there's several mutually exclusive combinations of these options which would be ambiguous, and are thus not allowed (will result in an immediately-thrown error upon calling `wrap(..)`), such as `errfcb: false`, `params_first: true, params_last: true`, etc. **Just avoid these.** Also, `params_first: false` is allowed, and just means `params_last: true`, but the latter is more preferable to the former. 87 | 88 | ### Wrapping A Generator 89 | 90 | If you pass `gen:true` as an option, it overrides all other options, and instead returns back a function that creates a new *asynquence* sequence with the `runner(..)` plugin (see below) wired to run the generator you passed in. Whatever arguments you pass to the wrapper will pass into the generator (accessed via `token.messages` -- again, see `runner(..)` plugin below). 91 | 92 | ```js 93 | var g = ASQ.wrap(function*(token){ 94 | var x = 1; 95 | for (var i=0; i < token.messages.length; i++) { 96 | x = yield (x * token.messages[i]); 97 | } 98 | },{ gen:true }); 99 | 100 | g(2,3,4) 101 | .val(function(msg){ 102 | console.log(msg); // 24 103 | }); 104 | 105 | g(2,3,4,5) 106 | .val(function(msg){ 107 | console.log(msg); // 120 108 | }); 109 | ``` 110 | 111 | The wrapper can be called one or many times, and each time will create and return a new sequence to run the generator. 112 | 113 | **Note:** See the `spread` wrapping option to spread out `token.messages` as arguments instead of passing in `token`. 114 | 115 | ## Gate-step Variations 116 | 117 | * `any(..)` is like `gate(..)`, which waits for all segments to complete, except **just one segment has to eventually succeed** to proceed on the main sequence. 118 | * `first(..)` is like `any(..)`, except **as soon as any segment succeeds**, the main sequence proceeds (ignoring subsequent results from other segments). 119 | * `race(..)` is like `first(..)`, except the main sequence proceeds **as soon as any segment completes** (either success or failure). 120 | * `last(..)` is like `any(..)`, except only **the latest segment to complete successfully** sends its message(s) along to the main sequence. 121 | * `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). 122 | * `map(arr, eachFn)` allows an asynchronous mapping of an array's values to another set of values. `map(..)` constructs a gate of segments, one for each item in `arr`. Each segment invokes `eachFn(..)` for the respective item in the array. 123 | 124 | `eachFn(item, doneTrigger, ..)` receives the respective `item` in the array and a `doneTrigger` to invoke with the new value to map back to that array position. **Note:** If multiple values are passed, that item's value will be an array (*asynquence* message wrapper) collection of the values passed. 125 | 126 | Just like with normal gates, `eachFn(..)` also receives any sequence messages passed forward from the previous main sequence step, such as `eachFn(item, doneTrigger, msg1, msg2, ..)`. And, if any segment causes an error, the rest of the `map(..)` fails and the main sequence is flagged as error'd. 127 | 128 | If either `arr`, `eachFn` or both are not passed to `map(..)`, it will attempt to pull them from the value-message stream it received from the previous step. Even if it does so, any subsequent messages in the stream will still pass on to the `eachFn` callback. 129 | 130 | The final success message from a `map(..)` sequence step is the newly constructed array of mapped values. 131 | 132 | ## Sequence-step Variations 133 | 134 | * `until(..)` is like `then(..)`, except it **keeps re-trying until success** or `break()` (for loop semantics) before the main sequence proceeds. 135 | * `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: ... }`. 136 | * `waterfall(..)` is like a sequence of `then(..)`s, except the output from each step is tracked, and the aggregate of all steps' success messages thus far is the input messages to the next step (step 3 gets passed success output from both 1 and 2, etc). Thus, the final output success message(s) of `waterfall(..)` is the collection of all success messages from the waterfall's steps. 137 | 138 | An error anywhere along the waterfall behaves like an error in any sequence, immediately jumping to error state and aborting any further success progression. 139 | 140 | ### `pThen` & `pCatch` Plugins 141 | 142 | `pThen` plugin provides a `pThen(..)` sequence method which is a cousin to the core built-in `then(..)`, but it works instead with similar semantics/behavior to native ES6 Promises. In other words, if you prefer the way `then(..)` works with Promises over *asynquence*'s `then(..)`, just use `pThen(..)` instead. **Note:** `pThen(..)` doesn't have the extra sugar capabilities like `then(..)` does, such as being able to accept sequences as direct parameters, etc -- it does just what Promise `then(..)` does. 143 | 144 | Also provided is `pCatch(..)` which has the same semantics as Promise `catch(..)`: it's literally the same as calling `pThen(null, ..)`. 145 | 146 | Example: 147 | 148 | ```js 149 | ASQ(21) 150 | .pThen(function(msg){ 151 | return msg * 2; 152 | }) 153 | .pThen(function(msg){ 154 | return ASQ(function(done){ 155 | setTimeout(function(){ 156 | done(msg); 157 | },100); 158 | }); 159 | }) 160 | .pThen(function(msg){ 161 | return new Promise(function(resolve,reject){ 162 | setTimeout(function(){ 163 | reject("Oops:" + msg); 164 | },100); 165 | }); 166 | }) 167 | // sequence is now in error state! 168 | .pThen( // or .pCatch(... 169 | null, 170 | function(err) { 171 | return err.toUpperCase(); 172 | } 173 | ) 174 | // sequence no longer in error state since 175 | // `pThen`/`pCatch` registered an error handler 176 | // which handled the sequence error. 177 | .pThen( 178 | function(msg) { 179 | throw msg; 180 | } 181 | ) 182 | // sequence is now in error state again! 183 | .pCatch( // or .pThen(null, ... 184 | function(err){ 185 | console.log(err); // "OOPS:42" 186 | return "Cool"; 187 | } 188 | ) 189 | // sequence no longer in error state 190 | .val(function(msg){ 191 | console.log(msg); // "Cool" 192 | }) 193 | .or(function(){}); // never called 194 | ``` 195 | 196 | You'll notice differences from the *asynquence* core `then(..)`, and how they match Promise `then(..)` behaviors instead: 197 | 198 | 1. `pThen(..)` takes a `success` and/or `failure` handler (both optional), rather than multiple `then` handlers. 199 | 2. The `success` handler is not provided the `done` trigger. 200 | 3. Instead, you return either an immediate value (which then passes on to the next step at the next cycle), or a sequence or promise for a value, in which case the sequence/promise is "unwrapped", and procession occurs only after it resolves. 201 | 4. If you register a `failure` handler via `pThen(..)` or `pCatch(..)`, then it swallows (so you can handle) any sequence errors to that point, and essentially resets the sequence back to success state after it is passes. 202 | 5. You can also return a value from a `failure` handler, which is the success message passed onto the next step. **Note:** Just like with Promises, a sequence/promise returned from an `failure` handler **is not "unwrapped"** -- it's just passed along as a normal value. 203 | 204 | ### `after` & `failAfter` Plugins 205 | 206 | `after` plugin provides a sequence instance method `after(..)` which inserts a delay into a sequence at that step. The first parameter is a number of milliseconds to wait. (Optional) additional parameters provide sequence messages to pass along (overriding previous sequence messages). Otherwise, previous sequence messages pass-through the delay automatically. 207 | 208 | `after` plugin also provides a static function version `ASQ.after(..)` which is the same as using the `ASQ().after(..)` method. 209 | 210 | 211 | ```js 212 | ASQ(42) // `42` gets discarded 213 | .after(500,"Hello","World!") 214 | .val(function(msg1,msg2){ 215 | console.log(msg1,msg2); // "Hello" "World!" 216 | }); 217 | 218 | ASQ.after(500) 219 | .val(function(){ 220 | console.log("Hello World!"); 221 | }); 222 | ``` 223 | 224 | `failAfter` plugin provides both the sequence method `failAfter(..)` and the static function `ASQ.failAfter(..)`, which work exactly like the `after` plugin methods above, but result in failure rather than success. 225 | 226 | The most common usage of the `failAfter` plugin is likely in combination with the `race(..)` plugin, to create "timeout" behavior: 227 | 228 | ```js 229 | // make a 2 sec timeout for some action 230 | ASQ() 231 | .race( 232 | doSomethingAsync(..), 233 | ASQ.failAfter(2000,"Timeout!") 234 | ) 235 | .val(function(){ 236 | // success! 237 | }) 238 | .or(function(err){ 239 | err; // "Timeout!" 240 | }); 241 | ``` 242 | 243 | ### `iterable-sequence` Plugin 244 | 245 | `iterable-sequence` plugin provides `ASQ.iterable()` for creating iterable sequences. See [Iterable Sequences](https://github.com/getify/asynquence/blob/master/README.md#iterable-sequences) for more information, and examples: [sync loop](https://gist.github.com/getify/8211148#file-ex1-sync-iteration-js) and [async loop](https://gist.github.com/getify/8211148#file-ex2-async-iteration-js). 246 | 247 | ### `toPromise` Plugin 248 | 249 | `toPromise` plugin provides `.toPromise()` (takes zero parameters) on a *asynquence* sequence instance's API, which allows you to vend/fork a new **native `Promise`** that's chained off of your sequence. Use this plugin if you need to send an *asynquence* sequence instance into some other utility which requires a *thenable* or standard [Promises/A+ compliant](http://promisesaplus.com) promise. 250 | 251 | **Note:** The vended promise is forked off the sequence, leaving the original sequence intact, to be continuable as normal. The message(s) (both success and error) from the chain are passed along to the promise, but they are *also* retained in the sequence itself, as if the forked-off promise is ignored. 252 | 253 | Example: 254 | 255 | ```js 256 | // make an asynquence sequence to use 257 | var sq = ASQ(function(done){ 258 | setTimeout(function(){ 259 | done(42); // send 42 along as success message 260 | },100); 261 | }); 262 | 263 | // fork and deal with the native promise 264 | sq.toPromise() 265 | .then( 266 | // success 267 | function(msg){ 268 | console.log(msg); // 42 269 | }, 270 | // error 271 | function(err){ 272 | console.log(err); 273 | } 274 | ); 275 | 276 | // also continue with the original sequence 277 | sq 278 | .val(function(msg){ 279 | console.log(msg); // 42 280 | }); 281 | ``` 282 | 283 | The goal of *asynquence* is to provide everything you need for promises-based async flow control without you needing to expose and use native promises or other promise libraries/utilities. Theoretically, this plugin should only be used when *asynquence* is insufficient in some way. If you find yourself needing to regularly vend native promises from *asynquence*, perhaps *asynquence* needs to be extended to handle that use-case, so let us know! 284 | 285 | ----- 286 | 287 | If you're using *asynquence* in an older environment which doesn't have the native ES6 `Promise` built-in, but you still want to be able to use the `.toPromise()` utility, you need a `Promise` polyfill. There are plenty of choices out there, but a great one to consider is: 288 | 289 | [Native Promise Only](http://github.com/getify/native-promise-only) 290 | 291 | As long as either the native `Promise` is there, or that global has been spec-compliant polyfilled, this `toPromise` plugin can create promises off your *asynquence* sequences. 292 | 293 | ----- 294 | 295 | ### `errfcb` Plugin 296 | 297 | The `errfcb` plugin provides `errfcb()` on the main sequence instance API. Calling `errfcb()` creates a step in the sequence that will wait to proceed, and returns an "error-first" style (aka "node-style") callback to signal this waiting sequence step. The "error-first" callback is suitable for any utility that expects such a callback. 298 | 299 | If the "error-first" callback is then invoked with the first ("error") parameter having a truthy value, the main sequence is flagged for error as usual. The value of the "error" parameter is provided to `.or()` callbacks as the failure reason. 300 | 301 | If the "error" parameter has a falsy value, the main sequence proceeds as success. Any other values provided to the callback are passed through as normal messages to the main sequence, and can be accessed by the next step in the sequence. 302 | 303 | Example: 304 | 305 | ```js 306 | // Node.js: fs.readFile(..) wrapper 307 | function readFile(filename) { 308 | // setup an empty sequence (much like an empty 309 | // promise) 310 | var sq = ASQ(); 311 | 312 | // call Node.js' fs.readFile(), but pass in 313 | // an error-first callback that is automatically 314 | // wired into the sequence. 315 | fs.readFile( filename, sq.errfcb() ); 316 | 317 | // now, return our sequence/promise, which is waiting for the 318 | // fs.readFile() call to complete. 319 | return sq; 320 | } 321 | 322 | readFile("meaningoflife.txt") 323 | .then(..) // will happen after fs.readFile invokes the "error-first" callback 324 | .. 325 | ``` 326 | 327 | Low-level (contrived) example, to show how the pieces fit together: 328 | 329 | ```js 330 | var seq = ASQ(); 331 | var f = seq.errfcb(); // now the sequence is waiting --- go ahead and add steps 332 | seq 333 | .val( function(message) { console.log(message); } ) 334 | .or( function(err) { console.log('Bogus! ' + err); } ); 335 | 336 | f(null, "Hello, world!"); // Success (since null is falsy) --- prints `Hello, world!` 337 | //f("oops") // Prints `Bogus! oops` if you call this instead of the preceding line 338 | ``` 339 | 340 | ### `runner` Plugin 341 | 342 | `runner(..)` takes any combination of **iterable-sequence** or ES6 generator function (which will be iterated through step-by-step) or promise-producing function (like an ES7/ES2016 `async function`!). `runner(..)` can handle receiving either *asynquence* sequences, standard promises/thenables, thunks (see ["thunks" here](http://zef.me/6096/callback-free-harmonious-node-js)), or immediate values as the yielded/returned/resolved values. 343 | 344 | The generator/iterable-sequence/promise-producer will receive any value-messages from the previous sequence step (via the *control token* -- see [CSP-style Concurrency](#csp-style-concurrency) below for explanation), and the final yielded/returned/resolved value will be passed along as the success message(s) to the next main sequence step. Error(s) if any will flag the main sequence as error, with error messages passed along as expected. 345 | 346 | #### Using a generator: 347 | 348 | ```js 349 | function thunkDouble(x) { 350 | return function thunk(cb) { 351 | setTimeout(function(){ 352 | // cb is an error-first style callback 353 | cb(null,x * 2); 354 | },500); 355 | }; 356 | } 357 | 358 | function promiseDouble(x) { 359 | // using ES6 `Promise`s 360 | return new Promise(function(resolve,reject){ 361 | setTimeout(function(){ 362 | resolve(x * 2); 363 | },500); 364 | }); 365 | } 366 | 367 | function seqDouble(x) { 368 | return ASQ(function(done){ 369 | setTimeout(function(){ 370 | done(x * 2); 371 | },500); 372 | }); 373 | } 374 | 375 | ASQ(2) 376 | .runner(function *step(token){ 377 | // extract message from control-token so 378 | // we can operate on it 379 | var x = token.messages[0]; // 2 380 | 381 | while (x < 100) { 382 | if (x < 10) { 383 | x = yield thunkDouble(x); // 4 8 16 384 | } 385 | else if (x < 40) { 386 | x = yield promiseDouble(x); // 32 387 | } 388 | else { 389 | x = yield seqDouble(x); // 64 128 390 | } 391 | } 392 | }) 393 | .val(function(num){ 394 | console.log(num); // 128 395 | }); 396 | ``` 397 | 398 | #### Using an iterable-sequence: 399 | 400 | ```js 401 | function thunkDouble(x) { 402 | return function thunk(cb) { 403 | setTimeout(function(){ 404 | // cb is an error-first style callback 405 | cb(null,x * 2); 406 | },500); 407 | }; 408 | } 409 | 410 | function promiseDouble(x) { 411 | // using ES6 `Promise`s 412 | return new Promise(function(resolve,reject){ 413 | setTimeout(function(){ 414 | resolve(x * 2); 415 | },500); 416 | }); 417 | } 418 | 419 | function seqDouble(x) { 420 | return ASQ(function(done){ 421 | setTimeout(function(){ 422 | done(x * 2); 423 | },500); 424 | }); 425 | } 426 | 427 | ASQ(2) 428 | .runner( 429 | ASQ.iterable() 430 | .then(function(token){ 431 | // extract message from control-token so 432 | // we can operate on it 433 | return token.messages[0]; // 2 434 | }) 435 | .then(thunkDouble) // 4 436 | .then(promiseDouble) // 8 437 | .then(seqDouble) // 16 438 | ) 439 | .val(function(num){ 440 | console.log(num); // 16 441 | }); 442 | ``` 443 | 444 | #### Using a promise-producing function: 445 | 446 | ```js 447 | function promiseDouble(x) { 448 | // using ES6 `Promise`s 449 | return new Promise(function(resolve,reject){ 450 | setTimeout(function(){ 451 | resolve(x * 2); 452 | },500); 453 | }); 454 | } 455 | 456 | ASQ(2) 457 | .runner( 458 | // ES7 `async function` 459 | async function step(token) { 460 | var x = token.messages[0]; // 2 461 | x = await promiseDouble(x); // 4 462 | x = await promiseDouble(x); // 8 463 | x = await promiseDouble(x); // 16 464 | return x; 465 | } 466 | ) 467 | .val(function(num){ 468 | console.log(num); // 16 469 | }); 470 | ``` 471 | 472 | **Note:** Any promise-returning function will work the same way here, but the ES7 `async function` syntax is illustrated above. 473 | 474 | #### CSP-style Concurrency 475 | 476 | `runner(..)` can accept 2 or more generators (or iterable-sequences) that you can cooperatively interleave execution of, which lets you leverage a simple form of CSP-style coroutine concurrency (aka **"cooperative multitasking"**). 477 | 478 | Generators/iterable-sequences will receive a *control token* with a messages channel (`.messages` property is a simple array) to use for passing messages back and forth as the coroutines interleave. 479 | 480 | If you `yield` (or `return` in the case of iterable-sequences) that *control token* back (or a sequence/promise that eventually produces it), then you will signal to transfer control to the next (round-robbin ordering style) generator/sequence in the concurrency-grouping. 481 | 482 | Otherwise, yielding/returning of any other type of value, **including a sequence/promise**, will retain control with the current generator/iterator-step. 483 | 484 | You can also call `.add(..)` on the *control token* to add one or more generators/iterable-sequences to the concurrency-grouping: 485 | 486 | ```js 487 | // promise to double `v` in 1000 ms 488 | function double(v) { 489 | return new Promise(function(resolve,reject){ 490 | setTimeout(function(){ 491 | resolve(v * 2); 492 | },1000); 493 | }); 494 | } 495 | 496 | function makeGen(x,y) { 497 | return function*(token){ 498 | token.messages.push( yield double(x) ); 499 | yield token; 500 | token.messages.push( yield double(y) ); 501 | }; 502 | } 503 | 504 | ASQ() 505 | .runner( 506 | function*(token) { 507 | token.add( 508 | makeGen(10,20), 509 | makeGen(100,200) 510 | ); 511 | while (token.messages.length < 4) { 512 | yield token; 513 | } 514 | yield token.messages; 515 | } 516 | ) 517 | .val(function(msg){ 518 | console.log(msg); // [ 20, 200, 40, 400 ] 519 | }); 520 | ``` 521 | 522 | With both generators and iterable-sequences, the last *final* non-`undefined` value that is yielded/returned from the concurrency-grouping run will be the forward-passed message(s) to the next step in your main *asynquence* chain. 523 | 524 | If you want to pass on the channel messages from your generator run, end your last generator by `yield`ing out the `.messages` property of the *control token* (see above snippet). Likewise with iterable-sequences, `return` the channel messages from the last iterable-sequence step. 525 | 526 | To get a better sense of how this advanced functionality works, check out these examples: 527 | 528 | * [State Machine](http://jsbin.com/luron/2/edit?js,console) with simple generator co-routines (hidden CSP) 529 | * [Ping Pong](http://jsbin.com/qutabu/4/edit?js,output) (from [js-csp](https://github.com/ubolonton/js-csp/blob/master/README.md#examples) and the [go ping-pong](http://talks.golang.org/2013/advconc.slide#6) example) 530 | * [Two generators paired as CSP-style co-routines](https://gist.github.com/getify/10172207) 531 | 532 | #### go-Style CSP API Emulation 533 | 534 | If you've heard of go-style CSP concurrency, such as in [Clojure's core.async](https://clojure.github.io/core.async/), or in various JS ports such as [@jlongster](http://github.com/jlongster)'s [js-csp fork](https://github.com/jlongster/js-csp) (also, [read his blog post](http://jlongster.com/Taming-the-Asynchronous-Beast-with-CSP-in-JavaScript)) of [ubolonton's js-csp](https://github.com/ubolonton/js-csp), *asynquence* has a (nearly-identical) API emulation layer that you can drop on top of *asynquence*'s CSP-flavored `runner(..)` mechanism described above to express channel-based concurrency. 535 | 536 | For example: 537 | 538 | ```js 539 | ASQ() 540 | .runner( 541 | ASQ.csp.go(function*(ch){ 542 | console.log("sending value"); 543 | yield ASQ.csp.put(ch,42); 544 | console.log("send complete"); 545 | }), 546 | ASQ.csp.go(function*(ch){ 547 | console.log("waiting..."); 548 | yield ASQ.csp.take( ASQ.csp.timeout(1000) ); 549 | console.log( 550 | "received value:", 551 | yield ASQ.csp.take(ch) 552 | ); 553 | }) 554 | ) 555 | .val(function(){ 556 | console.log("all done"); 557 | }); 558 | 559 | // sending value 560 | // waiting... 561 | // received value: 42 562 | // send complete 563 | // all done 564 | ``` 565 | 566 | As you can see, by calling `yield ASQ.csp.put(..)`, you're blocking that coroutine (aka "goroutine") while it attempts to send the value on the channel. The other coroutine doesn't take the value right away (it waits 1000ms). Then `yield ASQ.csp.take(..)` blocks until a value can be taken from the channel. 567 | 568 | In this case, `42` is already waiting to be sent, but if there were no value yet, that coroutine would block and wait. Once the value is taken and the second coroutine finishes, the first coroutine is unblocked and it finishes as well. 569 | 570 | The main concept with go-style CSP -- channel-based concurrency -- is that you use `put(..)`s and `take(..)`s on a shared channel (like a message stream) to coordinate interactions across multiple coroutines -- implicit transfers of control. Both `put(..)` and `take(..)` block, so that regardless of which action is taken "first", both must pair before the message can be sent across the channel. 571 | 572 | To use the go-style API emulation layer, you'll need (at least) the `iterable()` and `pThen()`/`pCatch()` contrib plugins. 573 | 574 | In the browser: 575 | ```html 576 | 577 | 578 | 579 | ``` 580 | 581 | In node: 582 | ```js 583 | var ASQ = require("asynquence"); 584 | 585 | require("asynquence-contrib"); 586 | require("asynquence-contrib/asq-go-csp.js"); 587 | ``` 588 | 589 | go-style CSP can be a very powerful abstraction for certain concurrency tasks, so using this API emulation layer gives you even more choices for expressing and managing async flow control in your JS programs. 590 | 591 | Check out [several more examples of go-style CSP](https://gist.github.com/getify/e0d04f1f5aa24b1947ae). 592 | 593 | ### `react` Plugin 594 | 595 | Consider this kind of ugly code: 596 | 597 | ```js 598 | $("#button").click(function(evt){ 599 | ASQ(this.id) 600 | .then(..) 601 | .seq(..) 602 | .then(..) 603 | .val(..) 604 | }); 605 | ``` 606 | 607 | Each time the button is clicked, a new sequence is defined and executed to "react" to the event. But it's a little awkward and ugly that the sequence must be (re)defined each time, *inside* the event listener. 608 | 609 | The `react` plugin separates the capabilities of listening for events and of responding to them, providing first-class syntactic support for the *asynquence* "reactive sequence" pattern, inspired by [RxJS Reactive Observables](http://rxjs.codeplex.com/). It essentially combines *asynquence*'s flow-control with repeatable event handling. 610 | 611 | 1. `react(..)` accepts a listener setup handler, which will receive a reactive trigger (called `proceed` in the snippet below) that event listener(s) "react" with by invoking. It will also receive a function you can call one or more times to register a *teardown* handler (to unbind event handlers, etc). 612 | 2. The rest of the chain appears as a (mostly) normal *asynquence* sequence, which will then be repeat-executed each time a new sequence message is pumped. 613 | - **Note:** The following sequence methods and plugins are not present on a reactive sequence, as their usage would be invalid: `pipe(..)`, `fork(..)`, `errfcb(..)`, `pThen(..)`/`pCatch(..)`, and `toPromise(..)`. 614 | 615 | The `react` plugin reverses the paradigm of the first snippet, providing a way to specify the sequence externally and once, and have it be re-triggered each time an event fires. 616 | 617 | ```js 618 | var rsq = ASQ.react( 619 | // this listener setup handler will be called only once 620 | function setup(proceed,registerTeardownHandler){ 621 | // fire off a new sequence for each click 622 | function handler(evt) { 623 | // we can call `proceed(..)` (or whatever you want 624 | // to call the param!) every time our stream/event 625 | // fires, instead of just once like normal promise 626 | // resolution 627 | proceed(this.id); 628 | } 629 | 630 | $("#button").click(handler); 631 | 632 | // register a handler to be called when tearing down 633 | // the reactive sequence handling 634 | registerTeardownHandler(function(){ 635 | $("#button").unbind("click",handler); 636 | }); 637 | 638 | // inside our `setup` handler, `this` will point to 639 | // the reactive sequence, which has a `stop()` method 640 | // that tears down the reactive sequence handling 641 | EVTHUB.on("finish",this.stop); 642 | } 643 | ) 644 | // each time our reactive event fires, 645 | // process the rest of this sequence 646 | .then(..) 647 | .seq(..) 648 | .then(..) 649 | .val(..); 650 | 651 | // later, to stop the reactive sequence handling: 652 | EVTHUB.on("totally-done",rsq.stop); 653 | ``` 654 | 655 | Inside the `react(..)` listener setup function, you can set up as many listeners for any kind of events (ajax, timers, click handlers, etc) as you want, and for each, all you need to do to fire off the sequence is call the `proceed(..)` (or whatever you want to name it!) callback. Whatever messages you pass to `proceed(..)` will pass along to the first step of the sequence instance. 656 | 657 | Calling `stop()` on a reactive sequence triggers any registered teardown handlers and permanently stops all activity for that sequence. The reactive sequence also has `pause()` and `resume()` methods to temporarily teardown and then restart a sequence's activity. **Note:** A paused sequence emulates a similar notion to a "cold observable", where as a running sequence is like a "hot observable". 658 | 659 | The reactive sequence API can be extended with a new instance method by calling `ASQ.react.extend(..)`. The first argument is the API method name and the second argument is a build function that defines the extension. This build function receives the current API as its only argument and must return the newly defined API method. This extensibility works almost identically to extending the main *asynquence* instances, but only affects reactive sequences. 660 | 661 | The `proceed` function has two helpers on it for dealing with streams (particularly node streams): `proceed.onStream(..)` and `proceed.unStream(..)`. `onStream(..)` takes one or more streams and subscribes the `data` and `error` events to call the `proceed` function. `unStream(..)` takes one or more streams to unsubscribe, so you would likely use it in a registered teardown handler. For example: 662 | 663 | ```js 664 | var rsq = ASQ.react(function(proceed,registerTeardownHandler){ 665 | proceed.onStream( mydatastream ); 666 | 667 | registerTeardownHandler(function(){ 668 | proceed.unStream( mydatastream ); 669 | }); 670 | }) 671 | .val(function(v){ 672 | if (v instanceof Error) throw v; 673 | // .. 674 | }) 675 | // .. 676 | .or(function(err){ 677 | console.log(err); 678 | }); 679 | ``` 680 | 681 | For a more real-world type of example, see [reactive sequences + `gate()`](http://jsbin.com/rozipaki/11/edit?js,output). Here's [another example](https://gist.github.com/getify/bba5ec0de9d6047b720e), which handles http request/response streams with reactive sequences. 682 | 683 | #### `react` Helpers 684 | 685 | The `reactHelpers` plugin includes several very useful helpers for the reactive sequences. 686 | 687 | ##### Reactive Sequence/RxJS Conversion 688 | 689 | Some utilities for interoperating between asynquence reactive sequences and RxJS Observables: 690 | 691 | * `toObservable()` is a sequence method on a normal asynquence sequence or a reactive sequence that produces an RxJS Observable (requires RxJS to be present) 692 | * `ASQ.react.fromObservable(..)` static utility that receives an RxJS-compatible Observable and turns it into a reactive sequence. 693 | 694 | ##### Directly Pumping Sequence Messages 695 | 696 | Similar to RxJS Subjects, reactive sequences can be directly pumped with sequence messages. 697 | 698 | * `ASQ.react.of(..)` creates a new reactive sequence as if produced by `ASQ.react(..)`, but if you provide one or more values as arguments, they are pumped as initial messages in the sequence. 699 | * `push(..)` on a reactive sequence instance will pump new messages into the sequence at any time. 700 | 701 | ##### Composition 702 | 703 | Some utilities for combining (aka composing) multiple reactive sequences: 704 | 705 | * `ASQ.react.all(..)` (alias `zip(..)` as with RxJS) creates a new reactive sequence that listens to one or more reactive sequences, and fires an event (with all messages included) whenever *all* observed sequences have fired an event. Each sequence's event messages are buffered in case the sequences are producing at different rates. 706 | * `ASQ.react.allLatest(..)`: same as `all(..)` except buffer size of 1, so it only keeps the latest message from each sequence. 707 | * `ASQ.react.latest(..)` (alias `combineLatest(..)` as with RxJS) is the same as `all(..)` except no buffering is done -- only the *latest* message from each sequence is kept. 708 | * `ASQ.react.any(..)` (alias `merge(..)` as with RxJS) creates a new reactive sequence that listens to one or more reactive sequences, and fires as soon as *any* observed sequence fires an event. 709 | 710 | Because of how `ASQ.react.all(..)` and `ASQ.react.any(..)` operate, you can effectively *duplicate* a reactive sequence simply by passing only it to either of the utilities. 711 | 712 | ###### Transformation 713 | 714 | Some utilities for transforming/mapping/projecting individual reactive sequences to new sequences: 715 | 716 | * `ASQ.react.distinct(rsq)`: creates a new reactive sequence that listens to a reactive sequence, and only fires whenever a *distinct* (ignoring duplicates with simple, shallow comparison) event message comes through from the observed sequence events. 717 | * `ASQ.react.distinctConsecutive(..)` (alias `distinctUntilChanged(..)` as with RxJS): same as `distinct(..)`, but ignores only consecutive duplicate (simple, shallow comparison) event messages from a single sequence. 718 | * `ASQ.react.filter(..)`: creates a new reactive sequence that listens to a reactive sequence, and only fires whenever an event message is not filtered out. 719 | 720 | A great way to visualize how these different reactive sequence compositions/transformations work is [RxMarbles](http://rxmarbles.com/). 721 | 722 | ## Using Contrib Plugins 723 | 724 | In the browser, include the `contrib.js` file along with the *asynquence* library file (`asq.js`). Doing so automatically extends the API with the plugins. 725 | 726 | In Node.js, you install the `asynquence-contrib` package alongside the `asynquence` package. **Note:** The *asynquence-contrib* package will return the *asynquence* instance for you, so you technically only need this if using both: 727 | 728 | ```js 729 | // Note: requiring "asynquence" not strictly needed here, 730 | // since contrib will retrieve and return it automatically 731 | 732 | var ASQ = require("asynquence-contrib"); 733 | ``` 734 | 735 | They can then be used together directly, like this: 736 | 737 | ```js 738 | ASQ() 739 | .try(foo) 740 | .until(bar) 741 | .then(baz); 742 | ``` 743 | 744 | **Note:** If you load contrib bundle(s) that cannot find a peer *asynquence* top-level package to load and use, a dependency-injection function is instead returned, which expects to be called with either an *asynquence* instance, or a relative path specifying where to load it. 745 | 746 | ## Building Contrib Bundle 747 | 748 | There is a utility provided to bundle the contrib plugins. 749 | 750 | ``` 751 | bundle.js usage: 752 | bundle.js [ {OPTION} .. ] [ {PLUGIN-NAME} .. ] 753 | 754 | --help prints this help 755 | --wrapper=filename wrapper filename ("contrib-wrapper.js") 756 | --bundle=filename bundle filename ("contrib.src.js") 757 | --min-bundle=filename minified-bundle filename ("contrib.js") 758 | --exclude={PLUGIN-NAME} exclude a plugin from bundling 759 | 760 | If you don't pass any {PLUGIN-NAME} parameters, all available plugins 761 | (except any that are --exclude omitted) will be bundled. 762 | 763 | If you pass one or more {PLUGIN-NAME} parameters, only the ones 764 | specified (except any that are --exclude omitted) will be bundled. 765 | ``` 766 | 767 | `bundle.js` by default builds the unminified bundle `contrib.src.js`, and then builds (minifies) `contrib.js`. By default, this build includes all the `contrib/plugin.*.js` plugins. 768 | 769 | The recommended way to invoke this utility is via npm: 770 | 771 | ``` 772 | npm run build 773 | ``` 774 | 775 | Some plugins, like `goCSP`, use ES6 features that are transpiled to ES5 using [Babel](http://babeljs.io) for the `contrib.src.js` and `contrib.js` bundles. So, to use `goCSP` in a browser for example, you'll need to also load the Babel browser polyfill, which is available at `./node_modules/babel-core/browser-polyfill.min.js` (and use `polyfill.js` in Node). 776 | 777 | The npm package distribution also includes `contrib-es6.src.js`, which is the unminified and non-transpiled (original native ES6 code) bundle. Also included in the package is `contrib-common.js` (and `contrib-common.src.js`), which includes only these commonly used plugins: `after`, `iterable`, `race`, `runner`, `toPromise`, and `wrap`. 778 | 779 | You can build your own bundle and manually specify which plugins you want, by name. For example, to bundle only the `any`, `none`, and `try` plugins: 780 | 781 | ``` 782 | ./bundle.js any none try 783 | ``` 784 | 785 | By passing *option* parameters to the bundle script, you can override the default filenames used for the contrib plugin wrapper (`--wrapper=..`), bundle (`--bundle=..`), and minified-bundle (`--min-bundle=..`). These options are useful for creating multiple variations of the plugin bundle. 786 | 787 | ## License 788 | 789 | The code and all the documentation, unless otherwise noted, are released under the MIT license. 790 | 791 | http://getify.mit-license.org/ 792 | -------------------------------------------------------------------------------- /tests.js: -------------------------------------------------------------------------------- 1 | (function(name,context,dependency,definition){ 2 | if (typeof module !== "undefined" && module.exports) module.exports = definition(require(dependency)); 3 | else if (typeof define === "function" && define.amd) define([dependency],definition); 4 | else context[name] = definition(dependency); 5 | })("ASQ_tests",this,this.ASQ || "./asq.src.js",function(ASQ){ 6 | "use strict"; 7 | 8 | function defineTests(doneLogMsg) { 9 | 10 | function asyncDelayFn(delay) { 11 | return function(done) { 12 | setTimeout(done,delay); 13 | }; 14 | } 15 | 16 | function asyncDelaySeq(delay) { 17 | return ASQ(function(done){ 18 | setTimeout(done,delay); 19 | }); 20 | } 21 | 22 | function PASS(testDone,testLabel) { 23 | doneLogMsg(testLabel + ": PASSED")(); 24 | testDone(); 25 | } 26 | 27 | function FAIL(testDone,testLabel) { 28 | doneLogMsg(testLabel + ": FAILED")(); 29 | testDone.fail.apply(testDone,ARRAY_SLICE.call(arguments,2)); 30 | } 31 | 32 | var ARRAY_SLICE = Array.prototype.slice; 33 | var ø = Object.create(null); 34 | var tests = []; 35 | 36 | tests.push(function(testDone){ 37 | var label = "Core Test #1", timeout, ASQ2; 38 | 39 | ASQ() 40 | .then(asyncDelayFn(100)) 41 | .gate(function(done){ 42 | done(1,2); 43 | }) 44 | .val(function(msg){ 45 | if (!( 46 | ASQ.isMessageWrapper( msg ) && 47 | !ASQ.isMessageWrapper( ASQ() ) && 48 | !ASQ.isMessageWrapper( [3,4] ) && 49 | ASQ.isSequence( ASQ() ) && 50 | !ASQ.isSequence( msg ) && 51 | !ASQ.isSequence( {} ) 52 | )) { 53 | clearTimeout(timeout); 54 | var args = ARRAY_SLICE.call(arguments); 55 | args.unshift(testDone,label); 56 | FAIL.apply(FAIL,args); 57 | } 58 | }) 59 | .then(function(){ 60 | clearTimeout(timeout); 61 | PASS(testDone,label); 62 | }) 63 | .onerror(function(){ 64 | clearTimeout(timeout); 65 | var args = ARRAY_SLICE.call(arguments); 66 | args.unshift(testDone,label); 67 | FAIL.apply(FAIL,args); 68 | }); 69 | 70 | timeout = setTimeout(function(){ 71 | FAIL(testDone,label + " (from timeout)"); 72 | },2000); 73 | }); 74 | tests.push(function(testDone){ 75 | var label = "Core Test #2", timeout; 76 | 77 | ASQ() 78 | .then(asyncDelaySeq(50)) 79 | .then(function(done){ 80 | asyncDelayFn(100)(function(){ 81 | done("Hello World"); 82 | }); 83 | }) 84 | .then(function(_,msg1){ 85 | clearTimeout(timeout); 86 | if (msg1 === "Hello World") { 87 | PASS(testDone,label); 88 | } 89 | else { 90 | var args = ARRAY_SLICE.call(arguments); 91 | args.unshift(testDone,label); 92 | FAIL.apply(FAIL,args); 93 | } 94 | }) 95 | .or(function(){ 96 | clearTimeout(timeout); 97 | var args = ARRAY_SLICE.call(arguments); 98 | args.unshift(testDone,label); 99 | FAIL.apply(FAIL,args); 100 | }); 101 | 102 | timeout = setTimeout(function(){ 103 | FAIL(testDone,label + " (from timeout)"); 104 | },2000); 105 | }); 106 | tests.push(function(testDone){ 107 | var label = "Core Test #3", timeout; 108 | 109 | ASQ() 110 | .then(function(done){ 111 | asyncDelayFn(100)(function(){ 112 | done("Hello"); 113 | }); 114 | }) 115 | .then(function(done,msg1){ 116 | asyncDelayFn(100)(function(){ 117 | done(msg1,"World"); 118 | }); 119 | }) 120 | .then(function(_,msg1,msg2){ 121 | clearTimeout(timeout); 122 | if (msg1 === "Hello" && msg2 === "World") { 123 | PASS(testDone,label); 124 | } 125 | else { 126 | var args = ARRAY_SLICE.call(arguments); 127 | args.unshift(testDone,label); 128 | FAIL.apply(FAIL,args); 129 | } 130 | }) 131 | .or(function(){ 132 | clearTimeout(timeout); 133 | var args = ARRAY_SLICE.call(arguments); 134 | args.unshift(testDone,label); 135 | FAIL.apply(FAIL,args); 136 | }); 137 | 138 | timeout = setTimeout(function(){ 139 | FAIL(testDone,label + " (from timeout)"); 140 | },2000); 141 | }); 142 | tests.push(function(testDone){ 143 | var label = "Core Test #4", timeout; 144 | 145 | ASQ() 146 | .then(function(done){ 147 | asyncDelayFn(100)(function(){ 148 | done.fail("Hello","World"); 149 | }); 150 | }) 151 | .then(function(){ 152 | clearTimeout(timeout); 153 | var args = ARRAY_SLICE.call(arguments); 154 | args.unshift(testDone,label); 155 | FAIL.apply(FAIL,args); 156 | }) 157 | .or(function(msg1,msg2){ 158 | clearTimeout(timeout); 159 | if (msg1 === "Hello" && msg2 === "World") { 160 | PASS(testDone,label); 161 | } 162 | else { 163 | var args = ARRAY_SLICE.call(arguments); 164 | args.unshift(testDone,label); 165 | FAIL.apply(FAIL,args); 166 | } 167 | }); 168 | 169 | timeout = setTimeout(function(){ 170 | FAIL(testDone,label + " (from timeout)"); 171 | },2000); 172 | }); 173 | tests.push(function(testDone){ 174 | var label = "Core Test #5", timeout; 175 | 176 | ASQ() 177 | .then(function(done){ 178 | done.a.b; // throwing JS error to make sure it's caught and propagated 179 | }) 180 | .then(function(){ 181 | clearTimeout(timeout); 182 | var args = ARRAY_SLICE.call(arguments); 183 | args.unshift(testDone,label); 184 | FAIL.apply(FAIL,args); 185 | }) 186 | .or(function(msg1){ 187 | clearTimeout(timeout); 188 | if (msg1 instanceof Error) { 189 | PASS(testDone,label); 190 | } 191 | else { 192 | var args = ARRAY_SLICE.call(arguments); 193 | args.unshift(testDone,label); 194 | FAIL.apply(FAIL,args); 195 | } 196 | }); 197 | 198 | timeout = setTimeout(function(){ 199 | FAIL(testDone,label + " (from timeout)"); 200 | },2000); 201 | }); 202 | tests.push(function(testDone){ 203 | var label = "Core Test #5b"; 204 | 205 | ASQ() 206 | .then(function(done){ 207 | // throwing JS error to make sure it's caught and propagated 208 | throw ASQ.messages("Oops","I","did","it","again!"); 209 | }) 210 | .then(function(){ 211 | var args = ARRAY_SLICE.call(arguments); 212 | args.unshift(testDone,label); 213 | FAIL.apply(FAIL,args); 214 | }) 215 | .or(function(msg1,msg2,msg3,msg4,msg5){ 216 | if (msg1 === "Oops" && 217 | msg2 === "I" && 218 | msg3 === "did" && 219 | msg4 === "it" && 220 | msg5 === "again!" 221 | ) { 222 | PASS(testDone,label); 223 | } 224 | else { 225 | var args = ARRAY_SLICE.call(arguments); 226 | args.unshift(testDone,label); 227 | FAIL.apply(FAIL,args); 228 | } 229 | }); 230 | }); 231 | tests.push(function(testDone){ 232 | var label = "Core Test #5c"; 233 | 234 | ASQ() 235 | .then(function(done){ 236 | // throwing JS error to make sure it's caught and propagated 237 | throw ASQ.messages("Oops","I","did","it","again!"); 238 | }) 239 | .or(function(){ 240 | throw ASQ.messages("Oh","yeah!"); 241 | }) 242 | .or(function(msg1,msg2,msg3,msg4,msg5,msg6,msg7){ 243 | if (msg1 === "Oops" && 244 | msg2 === "I" && 245 | msg3 === "did" && 246 | msg4 === "it" && 247 | msg5 === "again!" && 248 | msg6 === "Oh" && 249 | msg7 === "yeah!" 250 | ) { 251 | PASS(testDone,label); 252 | } 253 | else { 254 | var args = ARRAY_SLICE.call(arguments); 255 | args.unshift(testDone,label); 256 | FAIL.apply(FAIL,args); 257 | } 258 | }); 259 | }); 260 | tests.push(function(testDone){ 261 | var label = "Core Test #6", timeout, delay_sq; 262 | 263 | delay_sq = asyncDelaySeq(600); 264 | 265 | ASQ() 266 | .then(asyncDelayFn(100)) 267 | // using the `all(..)` alias of `gate(..)` 268 | .all( 269 | asyncDelayFn(800), 270 | delay_sq, 271 | asyncDelayFn(700) 272 | ) 273 | .then(function(){ 274 | clearTimeout(timeout); 275 | PASS(testDone,label); 276 | }) 277 | .or(function(){ 278 | clearTimeout(timeout); 279 | var args = ARRAY_SLICE.call(arguments); 280 | args.unshift(testDone,label); 281 | FAIL.apply(FAIL,args); 282 | }); 283 | 284 | timeout = setTimeout(function(){ 285 | FAIL(testDone,label + " (from timeout)"); 286 | },2000); 287 | }); 288 | tests.push(function(testDone){ 289 | var label = "Core Test #7", timeout; 290 | 291 | ASQ() 292 | .then(function(done){ 293 | asyncDelayFn(100)(function(){ 294 | done("msg1","msg2"); 295 | }); 296 | }) 297 | .gate( 298 | function(done,msg1,msg2){ 299 | asyncDelayFn(200)(function(){ 300 | done(msg1,msg2,"Hello"); 301 | }); 302 | }, 303 | function(done,msg1,msg2){ 304 | asyncDelayFn(100)(function(){ 305 | done(msg1+" "+msg2+" World"); 306 | }); 307 | } 308 | ) 309 | .then(function(_,msg1,msg2){ 310 | clearTimeout(timeout); 311 | 312 | if (ASQ.isMessageWrapper(msg1) && 313 | msg1[0] === "msg1" && 314 | msg1[1] === "msg2" && 315 | msg1[2] === "Hello" && 316 | msg2 === "msg1 msg2 World" 317 | ) { 318 | PASS(testDone,label); 319 | } 320 | else { 321 | var args = ARRAY_SLICE.call(arguments); 322 | args.unshift(testDone,label); 323 | FAIL.apply(FAIL,args); 324 | } 325 | }) 326 | .or(function(){ 327 | clearTimeout(timeout); 328 | var args = ARRAY_SLICE.call(arguments); 329 | args.unshift(testDone,label); 330 | FAIL.apply(FAIL,args); 331 | }); 332 | 333 | timeout = setTimeout(function(){ 334 | FAIL(testDone,label + " (from timeout)"); 335 | },2000); 336 | }); 337 | tests.push(function(testDone){ 338 | var label = "Core Test #8", timeout; 339 | 340 | ASQ() 341 | .then(asyncDelayFn(100)) 342 | .gate( 343 | function(done){ 344 | asyncDelayFn(100)(function(){ 345 | done("Hello"); 346 | }); 347 | }, 348 | function(done){ 349 | ASQ() 350 | .gate( 351 | asyncDelayFn(200), 352 | // insert a failed sequence into the gate 353 | ASQ.failed("World") 354 | ) 355 | .pipe(done); 356 | } 357 | ) 358 | .then(function(){ 359 | clearTimeout(timeout); 360 | var args = ARRAY_SLICE.call(arguments); 361 | args.unshift(testDone,label); 362 | FAIL.apply(FAIL,args); 363 | }) 364 | .or(function(msg1){ 365 | clearTimeout(timeout); 366 | 367 | if (msg1 === "World") { 368 | PASS(testDone,label); 369 | } 370 | else { 371 | var args = ARRAY_SLICE.call(arguments); 372 | args.unshift(testDone,label); 373 | FAIL.apply(FAIL,args); 374 | } 375 | }); 376 | 377 | timeout = setTimeout(function(){ 378 | FAIL(testDone,label + " (from timeout)"); 379 | },2000); 380 | }); 381 | tests.push(function(testDone){ 382 | var label = "Core Test #9", timeout; 383 | 384 | ASQ() 385 | .then(asyncDelayFn(100)) 386 | .then(function(done){ 387 | ASQ() 388 | .then(asyncDelayFn(100)) 389 | .then(function(done){ 390 | asyncDelayFn(100)(function(){ 391 | done("Hello"); 392 | }); 393 | }) 394 | .pipe(done); 395 | }) 396 | .then(function(done,msg1){ 397 | clearTimeout(timeout); 398 | 399 | if (msg1 === "Hello") { 400 | PASS(testDone,label); 401 | } 402 | else { 403 | var args = ARRAY_SLICE.call(arguments); 404 | args.unshift(testDone,label); 405 | FAIL.apply(FAIL,args); 406 | } 407 | }) 408 | .or(function(){ 409 | clearTimeout(timeout); 410 | var args = ARRAY_SLICE.call(arguments); 411 | args.unshift(testDone,label); 412 | FAIL.apply(FAIL,args); 413 | }); 414 | 415 | timeout = setTimeout(function(){ 416 | FAIL(testDone,label + " (from timeout)"); 417 | },2000); 418 | }); 419 | tests.push(function(testDone){ 420 | var label = "Core Test #10", timeout; 421 | 422 | ASQ() 423 | .then(asyncDelayFn(100)) 424 | .then(function(done){ 425 | ASQ() 426 | .then(asyncDelayFn(100)) 427 | .then( 428 | ASQ(function(done){ 429 | setTimeout(function(){ 430 | done.fail("Hello"); 431 | },100); 432 | }) 433 | ) 434 | .pipe(done); 435 | }) 436 | .then(function(done){ 437 | clearTimeout(timeout); 438 | var args = ARRAY_SLICE.call(arguments); 439 | args.unshift(testDone,label); 440 | FAIL.apply(FAIL,args); 441 | }) 442 | .or(function(msg1){ 443 | clearTimeout(timeout); 444 | 445 | if (msg1 === "Hello") { 446 | PASS(testDone,label); 447 | } 448 | else { 449 | var args = ARRAY_SLICE.call(arguments); 450 | args.unshift(testDone,label); 451 | FAIL.apply(FAIL,args); 452 | } 453 | }); 454 | 455 | timeout = setTimeout(function(){ 456 | FAIL(testDone,label + " (from timeout)"); 457 | },2000); 458 | }); 459 | tests.push(function(testDone){ 460 | var label = "Core Test #11", timeout; 461 | 462 | ASQ() 463 | .then(function(done){ 464 | done("Hello"); 465 | }) 466 | .then(function(done,msg1){ 467 | clearTimeout(timeout); 468 | 469 | if (msg1 === "Hello") { 470 | PASS(testDone,label); 471 | } 472 | else { 473 | var args = ARRAY_SLICE.call(arguments); 474 | args.unshift(testDone,label); 475 | FAIL.apply(FAIL,args); 476 | } 477 | }) 478 | .or(function(){ 479 | clearTimeout(timeout); 480 | var args = ARRAY_SLICE.call(arguments); 481 | args.unshift(testDone,label); 482 | FAIL.apply(FAIL,args); 483 | }); 484 | 485 | timeout = setTimeout(function(){ 486 | FAIL(testDone,label + " (from timeout)"); 487 | },2000); 488 | }); 489 | tests.push(function(testDone){ 490 | var label = "Core Test #12", timeout; 491 | 492 | ASQ 493 | .failed("Hello") 494 | .then(function(done){ 495 | clearTimeout(timeout); 496 | var args = ARRAY_SLICE.call(arguments); 497 | args.unshift(testDone,label); 498 | FAIL.apply(FAIL,args); 499 | }) 500 | .or(function(msg1){ 501 | clearTimeout(timeout); 502 | 503 | if (msg1 === "Hello") { 504 | PASS(testDone,label); 505 | } 506 | else { 507 | var args = ARRAY_SLICE.call(arguments); 508 | args.unshift(testDone,label); 509 | FAIL.apply(FAIL,args); 510 | } 511 | }); 512 | 513 | timeout = setTimeout(function(){ 514 | FAIL(testDone,label + " (from timeout)"); 515 | },2000); 516 | }); 517 | tests.push(function(testDone){ 518 | var label = "Core Test #13", timeout; 519 | 520 | ASQ() 521 | .then(function(done){ 522 | done("Hello"); 523 | done.fail("World"); 524 | }) 525 | .then(function(done,msg1){ 526 | clearTimeout(timeout); 527 | 528 | if (msg1 === "Hello") { 529 | PASS(testDone,label); 530 | } 531 | else { 532 | var args = ARRAY_SLICE.call(arguments); 533 | args.unshift(testDone,label); 534 | FAIL.apply(FAIL,args); 535 | } 536 | }) 537 | .or(function(){ 538 | clearTimeout(timeout); 539 | var args = ARRAY_SLICE.call(arguments); 540 | args.unshift(testDone,label); 541 | FAIL.apply(FAIL,args); 542 | }); 543 | 544 | timeout = setTimeout(function(){ 545 | FAIL(testDone,label + " (from timeout)"); 546 | },2000); 547 | }); 548 | tests.push(function(testDone){ 549 | var label = "Core Test #14", timeout; 550 | 551 | ASQ() 552 | .then(function(done){ 553 | done.fail("Hello"); 554 | done("World"); 555 | }) 556 | .then(function(){ 557 | clearTimeout(timeout); 558 | var args = ARRAY_SLICE.call(arguments); 559 | args.unshift(testDone,label); 560 | FAIL.apply(FAIL,args); 561 | }) 562 | .or(function(msg1){ 563 | clearTimeout(timeout); 564 | 565 | if (msg1 === "Hello") { 566 | PASS(testDone,label); 567 | } 568 | else { 569 | var args = ARRAY_SLICE.call(arguments); 570 | args.unshift(testDone,label); 571 | FAIL.apply(FAIL,args); 572 | } 573 | }); 574 | 575 | timeout = setTimeout(function(){ 576 | FAIL(testDone,label + " (from timeout)"); 577 | },2000); 578 | }); 579 | tests.push(function(testDone){ 580 | var label = "Core Test #15", timeout, sq2, sq3; 581 | 582 | function doSeq(msg1,msg2) { 583 | var seq = ASQ(); 584 | 585 | seq 586 | .then(asyncDelayFn(100)) 587 | .then(function(done){ 588 | done(msg2); 589 | }); 590 | 591 | return seq; 592 | } 593 | 594 | function doSeq2() { 595 | var seq = ASQ(); 596 | 597 | seq 598 | .then(asyncDelayFn(50)) 599 | .then(function(done){ 600 | done("Sweet"); 601 | }); 602 | 603 | return seq; 604 | } 605 | 606 | sq2 = doSeq2(); 607 | 608 | sq3 = ASQ.failed("Yep"); 609 | 610 | ASQ() 611 | .then(function(done){ 612 | asyncDelayFn(100)(function(){ 613 | done("Hello","World"); 614 | }); 615 | }) 616 | .seq(doSeq) 617 | .val(function(msg){ 618 | // did messages fail to flow through seq()? 619 | if (msg !== "World") { 620 | var args = ARRAY_SLICE.call(arguments); 621 | args.unshift(testDone,label); 622 | FAIL.apply(FAIL,args); 623 | } 624 | 625 | return "Ignored message"; 626 | }) 627 | // NOTE: passing in the sequence `sq2` itself 628 | .seq(sq2) 629 | .val(function(msg){ 630 | if (!( 631 | arguments.length === 1 && 632 | msg === "Sweet" 633 | )) { 634 | clearTimeout(timeout); 635 | var args = ARRAY_SLICE.call(arguments); 636 | args.unshift(testDone,label); 637 | FAIL.apply(FAIL,args); 638 | return; 639 | } 640 | 641 | return "Another ignored message"; 642 | }) 643 | // NOTE: passing in a failed sequence `sq3` itself 644 | .seq(sq3) 645 | .val(function(){ 646 | clearTimeout(timeout); 647 | var args = ARRAY_SLICE.call(arguments); 648 | args.unshift(testDone,label); 649 | FAIL.apply(FAIL,args); 650 | }) 651 | .or(function(msg){ 652 | clearTimeout(timeout); 653 | 654 | if (arguments.length === 1 && 655 | msg === "Yep" 656 | ) { 657 | PASS(testDone,label); 658 | } 659 | else { 660 | var args = ARRAY_SLICE.call(arguments); 661 | args.unshift(testDone,label); 662 | FAIL.apply(FAIL,args); 663 | } 664 | }); 665 | 666 | // Note: these should not affect the main sequence above, 667 | // since `sq2` and `sq3` should be tapped immediately 668 | // at time of `seq(..)` calls. 669 | sq2.val(function(msg){ 670 | if (!( 671 | arguments.length === 1 && 672 | msg === "Sweet" 673 | )) { 674 | clearTimeout(timeout); 675 | var args = ARRAY_SLICE.call(arguments); 676 | args.unshift(testDone,label); 677 | FAIL.apply(FAIL,args); 678 | return; 679 | } 680 | 681 | return "OOPS!"; 682 | }); 683 | 684 | sq3.or(function(msg){ 685 | if (!( 686 | arguments.length === 1 && 687 | msg === "Yep" 688 | )) { 689 | clearTimeout(timeout); 690 | var args = ARRAY_SLICE.call(arguments); 691 | args.unshift(testDone,label); 692 | FAIL.apply(FAIL,args); 693 | return; 694 | } 695 | 696 | throw "Uh oh!"; 697 | }) 698 | // Note: deferring because we don't actually care about 699 | // this error! 700 | .defer(); 701 | 702 | timeout = setTimeout(function(){ 703 | FAIL(testDone,label + " (from timeout)"); 704 | },2000); 705 | }); 706 | tests.push(function(testDone){ 707 | var label = "Core Test #16", timeout; 708 | 709 | function doSeq(msg1,msg2) { 710 | var seq = ASQ(); 711 | 712 | seq 713 | .then(asyncDelayFn(250)) 714 | // NOTE: calling doSeq2() to pass in ASQ instance itself 715 | .seq( doSeq2(msg2) ); 716 | 717 | return seq; 718 | } 719 | 720 | function doSeq2(msg) { 721 | var seq = ASQ(); 722 | 723 | seq 724 | .then(asyncDelayFn(50)) 725 | // NOTE: calling doSeq3() to pass in ASQ instance itself 726 | .seq( doSeq3(msg + "!") ); 727 | 728 | return seq; 729 | } 730 | 731 | function doSeq3(msg) { 732 | var seq = ASQ(); 733 | 734 | seq 735 | .then(asyncDelayFn(100)) 736 | .then(function(done){ 737 | done.fail(msg.toUpperCase()); 738 | }); 739 | 740 | return seq; 741 | } 742 | 743 | ASQ() 744 | .then(function(done){ 745 | asyncDelayFn(100)(function(){ 746 | done("Hello","World"); 747 | }); 748 | }) 749 | .seq(doSeq) 750 | .then(function(){ 751 | clearTimeout(timeout); 752 | var args = ARRAY_SLICE.call(arguments); 753 | args.unshift(testDone,label); 754 | FAIL.apply(FAIL,args); 755 | }) 756 | .or(function(msg){ 757 | clearTimeout(timeout); 758 | 759 | if (msg === "WORLD!") { 760 | PASS(testDone,label); 761 | } 762 | else { 763 | var args = ARRAY_SLICE.call(arguments); 764 | args.unshift(testDone,label); 765 | FAIL.apply(FAIL,args); 766 | } 767 | }); 768 | 769 | timeout = setTimeout(function(){ 770 | FAIL(testDone,label + " (from timeout)"); 771 | },2000); 772 | }); 773 | tests.push(function(testDone){ 774 | var label = "Core Test #17", timeout; 775 | 776 | ASQ() 777 | .then(asyncDelayFn(100)) 778 | .then(function(done){ 779 | asyncDelayFn(100)(function(){ 780 | done("Hello","World"); 781 | }); 782 | }) 783 | .val(function(msg1,msg2){ 784 | return msg1.toUpperCase() + " " + msg2.toUpperCase(); 785 | }) 786 | .then(function(_,msg1){ 787 | clearTimeout(timeout); 788 | 789 | if (msg1 === "HELLO WORLD") { 790 | PASS(testDone,label); 791 | } 792 | else { 793 | var args = ARRAY_SLICE.call(arguments); 794 | args.unshift(testDone,label); 795 | FAIL.apply(FAIL,args); 796 | } 797 | }) 798 | .or(function(){ 799 | clearTimeout(timeout); 800 | var args = ARRAY_SLICE.call(arguments); 801 | args.unshift(testDone,label); 802 | FAIL.apply(FAIL,args); 803 | }); 804 | 805 | timeout = setTimeout(function(){ 806 | FAIL(testDone,label + " (from timeout)"); 807 | },2000); 808 | }); 809 | tests.push(function(testDone){ 810 | var label = "Core Test #18", timeout; 811 | 812 | ASQ() 813 | .val(function(){ 814 | return ASQ.messages("Hello","World"); 815 | }) 816 | .then(function(_,msg1,msg2){ 817 | clearTimeout(timeout); 818 | 819 | if (msg1 === "Hello" && 820 | msg2 === "World" 821 | ) { 822 | PASS(testDone,label); 823 | } 824 | else { 825 | var args = ARRAY_SLICE.call(arguments); 826 | args.unshift(testDone,label); 827 | FAIL.apply(FAIL,args); 828 | } 829 | }) 830 | .or(function(){ 831 | clearTimeout(timeout); 832 | var args = ARRAY_SLICE.call(arguments); 833 | args.unshift(testDone,label); 834 | FAIL.apply(FAIL,args); 835 | }); 836 | 837 | timeout = setTimeout(function(){ 838 | FAIL(testDone,label + " (from timeout)"); 839 | },2000); 840 | }); 841 | tests.push(function(testDone){ 842 | var label = "Core Test #19", timeout; 843 | 844 | ASQ( 845 | "Hello", 846 | "World", 847 | function(done,msg1,msg2){ 848 | if ( 849 | arguments.length === 3 && 850 | msg1 === "Hello" && 851 | msg2 === "World" 852 | ) { 853 | done("So far so good"); 854 | } 855 | else { 856 | var args = ARRAY_SLICE.call(arguments); 857 | args.unshift(testDone,label); 858 | FAIL.apply(FAIL,args); 859 | } 860 | }, 861 | ASQ.messages("Yay","Nay"), 862 | function(done,msg1,msg2) { 863 | if ( 864 | arguments.length === 3 && 865 | msg1 === "Yay" && 866 | msg2 === "Nay" 867 | ) { 868 | done("Keep up the good work!"); 869 | } 870 | else { 871 | var args = ARRAY_SLICE.call(arguments); 872 | args.unshift(testDone,label); 873 | FAIL.apply(FAIL,args); 874 | } 875 | }, 876 | "Oh yeah" 877 | ) 878 | .val( 879 | function(msg){ 880 | if (!( 881 | arguments.length === 1 && 882 | msg === "Oh yeah" 883 | )) { 884 | var args = ARRAY_SLICE.call(arguments); 885 | args.unshift(testDone,label); 886 | FAIL.apply(FAIL,args); 887 | } 888 | }, 889 | "Ignored", 890 | "Also Ignored", 891 | ASQ.messages("Cool","Bro"), 892 | function(msg1,msg2){ 893 | if (!( 894 | arguments.length === 2 && 895 | msg1 === "Cool" && 896 | msg2 === "Bro" 897 | )) { 898 | var args = ARRAY_SLICE.call(arguments); 899 | args.unshift(testDone,label); 900 | FAIL.apply(FAIL,args); 901 | } 902 | } 903 | ) 904 | .val( 905 | "Nice", 906 | "Job", 907 | function(msg1,msg2){ 908 | clearTimeout(timeout); 909 | 910 | if (arguments.length === 2 && 911 | msg1 === "Nice" && 912 | msg2 === "Job" 913 | ) { 914 | PASS(testDone,label); 915 | } 916 | else { 917 | var args = ARRAY_SLICE.call(arguments); 918 | args.unshift(testDone,label); 919 | FAIL.apply(FAIL,args); 920 | } 921 | } 922 | ) 923 | .or(function(){ 924 | clearTimeout(timeout); 925 | var args = ARRAY_SLICE.call(arguments); 926 | args.unshift(testDone,label); 927 | FAIL.apply(FAIL,args); 928 | }); 929 | 930 | timeout = setTimeout(function(){ 931 | FAIL(testDone,label + " (from timeout)"); 932 | },2000); 933 | }); 934 | tests.push(function(testDone){ 935 | var label = "Core Test #20", timeout; 936 | 937 | function Pr(){ 938 | var args = ASQ.messages.apply(ø,arguments); 939 | return new Promise(function(resolve){ 940 | setTimeout(function(){ 941 | resolve(args.length > 1 ? args : args[0]); 942 | },10); 943 | }); 944 | } 945 | 946 | function bPr(){ 947 | var args = ASQ.messages.apply(ø,arguments); 948 | return new Promise(function(_,reject){ 949 | setTimeout(function(){ 950 | reject(args.length > 1 ? args : args[0]); 951 | },10); 952 | }); 953 | } 954 | 955 | ASQ("Hello") 956 | // generate 3 promises in succession, 957 | // using asynquence to chain them 958 | .promise(Pr,Pr,Pr) 959 | .val(function(msg){ 960 | if (!( 961 | arguments.length === 1 && 962 | msg === "Hello" 963 | )) { 964 | var args = ARRAY_SLICE.call(arguments); 965 | args.unshift(testDone,label); 966 | FAIL.apply(FAIL,args); 967 | } 968 | }) 969 | .promise(Pr("Hello","World"),Pr) 970 | .val(function(msg1,msg2){ 971 | if (!( 972 | arguments.length === 2 && 973 | msg1 === "Hello" && 974 | msg2 === "World" 975 | )) { 976 | var args = ARRAY_SLICE.call(arguments); 977 | args.unshift(testDone,label); 978 | FAIL.apply(FAIL,args); 979 | } 980 | 981 | return msg2.toUpperCase(); 982 | }) 983 | .promise(bPr) // Note: a broken promise! 984 | .val(function(){ 985 | clearTimeout(timeout); 986 | var args = ARRAY_SLICE.call(arguments); 987 | args.unshift(testDone,label); 988 | FAIL.apply(FAIL,args); 989 | }) 990 | .or(function(msg){ 991 | clearTimeout(timeout); 992 | 993 | if (arguments.length === 1 && 994 | msg === "WORLD" 995 | ) { 996 | PASS(testDone,label); 997 | } 998 | else { 999 | var args = ARRAY_SLICE.call(arguments); 1000 | args.unshift(testDone,label); 1001 | FAIL.apply(FAIL,args); 1002 | } 1003 | }); 1004 | 1005 | timeout = setTimeout(function(){ 1006 | FAIL(testDone,label + " (from timeout)"); 1007 | },2000); 1008 | }); 1009 | tests.push(function(testDone){ 1010 | var label = "Core Test #21", timeout; 1011 | 1012 | function Ef(err,msg,delay,cb) { 1013 | setTimeout(function(){ 1014 | if (!Array.isArray(err)) err = [err]; 1015 | if (!Array.isArray(msg)) msg = [msg]; 1016 | msg = err.concat(msg); 1017 | cb.apply(ø,msg); 1018 | },delay); 1019 | } 1020 | 1021 | ASQ(function(done){ 1022 | Ef(/*err=*/void 0,/*success=*/["Yay","Man"],100,done.errfcb); 1023 | }) 1024 | .val(function(msg1,msg2){ 1025 | if (!( 1026 | arguments.length === 2 && 1027 | msg1 === "Yay" && 1028 | msg2 === "Man" 1029 | )) { 1030 | var args = ARRAY_SLICE.call(arguments); 1031 | args.unshift(testDone,label); 1032 | FAIL.apply(FAIL,args); 1033 | } 1034 | }) 1035 | .gate( 1036 | function(done){ 1037 | Ef(/*err=*/void 0,/*success=*/void 0,100,done.errfcb); 1038 | }, 1039 | function(done){ 1040 | Ef(/*err=*/void 0,/*success=*/"Hello",200,done.errfcb); 1041 | }, 1042 | function(done){ 1043 | done.errfcb(/*err=*/void 0,/*success=*/"World","!"); 1044 | } 1045 | ) 1046 | .val(function(msg1,msg2,msg3){ 1047 | if (!( 1048 | arguments.length === 3 && 1049 | msg1 === undefined && 1050 | msg2 === "Hello" && 1051 | ASQ.isMessageWrapper(msg3) && 1052 | msg3.length === 2 && 1053 | msg3[0] === "World" && 1054 | msg3[1] === "!" 1055 | )) { 1056 | var args = ARRAY_SLICE.call(arguments); 1057 | args.unshift(testDone,label); 1058 | FAIL.apply(FAIL,args); 1059 | } 1060 | }) 1061 | .then(function(mainDone){ 1062 | ASQ(function(done){ 1063 | // force an "error" on this inner sequence 1064 | Ef(/*err=*/"Boo",/*success=*/"Ignored",100,done.errfcb); 1065 | }) 1066 | .then(function(){ 1067 | var args = ARRAY_SLICE.call(arguments); 1068 | args.unshift(testDone,label); 1069 | FAIL.apply(FAIL,args); 1070 | }) 1071 | .or(function(){ 1072 | mainDone.apply(ø,arguments); 1073 | }); 1074 | }) 1075 | .val(function(msg){ 1076 | if (!( 1077 | arguments.length === 1 && 1078 | msg === "Boo" 1079 | )) { 1080 | var args = ARRAY_SLICE.call(arguments); 1081 | args.unshift(testDone,label); 1082 | FAIL.apply(FAIL,args); 1083 | } 1084 | }) 1085 | .gate( 1086 | function(done){ 1087 | done("Ignored"); 1088 | }, 1089 | function(done){ 1090 | Ef(/*err=*/"All done",/*success=*/"Ignored 2",100,done.errfcb); 1091 | } 1092 | ) 1093 | .val(function(){ 1094 | var args = ARRAY_SLICE.call(arguments); 1095 | args.unshift(testDone,label); 1096 | FAIL.apply(FAIL,args); 1097 | }) 1098 | .or(function(msg){ 1099 | clearTimeout(timeout); 1100 | 1101 | if ( 1102 | arguments.length === 1 && 1103 | msg === "All done" 1104 | ) { 1105 | PASS(testDone,label); 1106 | } 1107 | else { 1108 | var args = ARRAY_SLICE.call(arguments); 1109 | args.unshift(testDone,label); 1110 | FAIL.apply(FAIL,args); 1111 | } 1112 | }); 1113 | 1114 | timeout = setTimeout(function(){ 1115 | FAIL(testDone,label + " (from timeout)"); 1116 | },2000); 1117 | }); 1118 | tests.push(function(testDone){ 1119 | var label = "Core Test #22", timeout, sq, sq2, sq3, sq4, sq5; 1120 | 1121 | sq = ASQ(function(done){ 1122 | setTimeout(function(){ 1123 | done("Hello"); 1124 | },10); 1125 | }); 1126 | 1127 | // first fork-listener 1128 | sq2 = sq.fork().val(function(msg){ 1129 | if (!( 1130 | arguments.length === 1 && 1131 | msg === "Hello" 1132 | )) { 1133 | var args = ARRAY_SLICE.call(arguments); 1134 | args.unshift(testDone,label); 1135 | FAIL.apply(FAIL,args); 1136 | } 1137 | }); 1138 | 1139 | // second fork-listener 1140 | sq3 = sq.fork().val(function(msg){ 1141 | if (!( 1142 | arguments.length === 1 && 1143 | msg === "Hello" 1144 | )) { 1145 | var args = ARRAY_SLICE.call(arguments); 1146 | args.unshift(testDone,label); 1147 | FAIL.apply(FAIL,args); 1148 | } 1149 | }); 1150 | 1151 | // main sequence-listener 1152 | sq.val(function(msg){ 1153 | if (!( 1154 | arguments.length === 1 && 1155 | msg === "Hello" 1156 | )) { 1157 | var args = ARRAY_SLICE.call(arguments); 1158 | args.unshift(testDone,label); 1159 | FAIL.apply(FAIL,args); 1160 | } 1161 | }); 1162 | 1163 | // test sending an error into the forks 1164 | sq.then(function(done){ 1165 | setTimeout(function(){ 1166 | done.fail("World"); 1167 | },10); 1168 | }); 1169 | 1170 | // second fork-listener 1171 | sq4 = sq.fork() 1172 | .val(function(){ 1173 | var args = ARRAY_SLICE.call(arguments); 1174 | args.unshift(testDone,label); 1175 | FAIL.apply(FAIL,args); 1176 | }) 1177 | .or(function(msg){ 1178 | if (!( 1179 | arguments.length === 1 && 1180 | msg === "World" 1181 | )) { 1182 | var args = ARRAY_SLICE.call(arguments); 1183 | args.unshift(testDone,label); 1184 | FAIL.apply(FAIL,args); 1185 | } 1186 | }); 1187 | 1188 | // third fork-listener 1189 | sq5 = sq.fork() 1190 | .val(function(){ 1191 | var args = ARRAY_SLICE.call(arguments); 1192 | args.unshift(testDone,label); 1193 | FAIL.apply(FAIL,args); 1194 | }) 1195 | .or(function(msg){ 1196 | if (!( 1197 | arguments.length === 1 && 1198 | msg === "World" 1199 | )) { 1200 | var args = ARRAY_SLICE.call(arguments); 1201 | args.unshift(testDone,label); 1202 | FAIL.apply(FAIL,args); 1203 | } 1204 | }); 1205 | 1206 | // main sequence listener 1207 | sq.val(function(){ 1208 | var args = ARRAY_SLICE.call(arguments); 1209 | args.unshift(testDone,label); 1210 | FAIL.apply(FAIL,args); 1211 | }) 1212 | .or(function(msg){ 1213 | var args = ARRAY_SLICE.call(arguments); 1214 | 1215 | // defer this error handling while the other 1216 | // forks are error-notified 1217 | setTimeout(function(){ 1218 | clearTimeout(timeout); 1219 | 1220 | if (args.length === 1 && 1221 | msg === "World" 1222 | ) { 1223 | PASS(testDone,label); 1224 | } 1225 | else { 1226 | args.unshift(testDone,label); 1227 | FAIL.apply(FAIL,args); 1228 | } 1229 | },0); 1230 | }); 1231 | 1232 | timeout = setTimeout(function(){ 1233 | FAIL(testDone,label + " (from timeout)"); 1234 | },2000); 1235 | }); 1236 | tests.push(function(testDone){ 1237 | var label = "Core Test #23", timeout, 1238 | sq1, sq2, seed = 10 1239 | ; 1240 | 1241 | function seqMessages(msg1,msg2,msg3) { 1242 | return ASQ(function(done){ 1243 | setTimeout(function(){ 1244 | done(msg1,msg2,msg3); 1245 | },25); 1246 | }); 1247 | } 1248 | 1249 | function promiseMessages(msg1,msg2,msg3) { 1250 | return new Promise(function(resolve){ 1251 | setTimeout(function(){ 1252 | resolve(ASQ.messages(msg1,msg2,msg3)); 1253 | },25); 1254 | }); 1255 | } 1256 | 1257 | sq1 = ASQ() 1258 | .seq(seqMessages) 1259 | .promise(promiseMessages) 1260 | .val(function(s1,s2,s3){ 1261 | // if any messages received, use them 1262 | seed += ((s1 + s2 + s3) || 0); 1263 | }) 1264 | .then(asyncDelayFn(100)) 1265 | .then(function(done){ 1266 | done(++seed); 1267 | }) 1268 | .gate( 1269 | function(done){ 1270 | done(++seed); 1271 | }, 1272 | function(done){ 1273 | done(++seed); 1274 | } 1275 | ); 1276 | 1277 | // duplicate a template of the sequence 1278 | sq2 = sq1.duplicate(); 1279 | 1280 | sq1.val(function(msg1,msg2){ 1281 | if (!( 1282 | msg1 === 12 && 1283 | msg2 === 13 1284 | )) { 1285 | clearTimeout(timeout); 1286 | var args = ARRAY_SLICE.call(arguments); 1287 | args.unshift(testDone,label); 1288 | FAIL.apply(FAIL,args); 1289 | } 1290 | }) 1291 | .then(function(){ 1292 | seed = 20; 1293 | 1294 | // unpause the duplicated sequence 1295 | sq2 = ASQ.unpause(sq2); 1296 | // inject some messages into the unpausing sequence 1297 | // hint: not a great idea, but supported 1298 | sq2.unpause(1,2); 1299 | sq2.unpause(3); 1300 | 1301 | // later, check to see if the sequence 1302 | // was indeed restarted and ran properly 1303 | setTimeout(function(){ 1304 | if (seed !== 29) { 1305 | clearTimeout(timeout); 1306 | var args = [testDone,label,"seed: " + seed]; 1307 | FAIL.apply(FAIL,args); 1308 | return; 1309 | } 1310 | 1311 | sq2.val(function(msg1,msg2){ 1312 | if (!( 1313 | msg1 === 28 && 1314 | msg2 === 29 1315 | )) { 1316 | clearTimeout(timeout); 1317 | var args = ARRAY_SLICE.call(arguments); 1318 | args.unshift(testDone,label); 1319 | FAIL.apply(FAIL,args); 1320 | } 1321 | }) 1322 | .then(function(){ 1323 | clearTimeout(timeout); 1324 | PASS(testDone,label); 1325 | }) 1326 | .or(function(){ 1327 | clearTimeout(timeout); 1328 | var args = ARRAY_SLICE.call(arguments); 1329 | args.unshift(testDone,label); 1330 | FAIL.apply(FAIL,args); 1331 | }); 1332 | },500); 1333 | }) 1334 | .or(function(){ 1335 | clearTimeout(timeout); 1336 | var args = ARRAY_SLICE.call(arguments); 1337 | args.unshift(testDone,label); 1338 | FAIL.apply(FAIL,args); 1339 | }); 1340 | 1341 | timeout = setTimeout(function(){ 1342 | FAIL(testDone,label + " (from timeout)"); 1343 | },2000); 1344 | }); 1345 | tests.push(function(testDone){ 1346 | var label = "Core Test #24", timeout, ASQ2, ASQ3; 1347 | 1348 | ASQ2 = ASQ.clone(); 1349 | 1350 | ASQ2._hello_ = "world"; 1351 | ASQ2.extend("foobar",function(){ return function(){}; }); 1352 | 1353 | ASQ3 = ASQ2.clone(); 1354 | 1355 | try { 1356 | ASQ().foobar(); 1357 | FAIL(testDone,label,"ASQ().foobar()"); 1358 | return; 1359 | } catch (err) {} 1360 | 1361 | try { 1362 | ASQ2().foobar(); 1363 | } 1364 | catch (err) { 1365 | FAIL(testDone,label,"ASQ2().foobar()",err,ASQ2(),ASQ2().foobar); 1366 | return; 1367 | } 1368 | 1369 | try { 1370 | ASQ3._hello_.length; 1371 | ASQ3().foobar(); 1372 | FAIL(testDone,label,"ASQ3",ASQ3); 1373 | return; 1374 | } catch (err) {} 1375 | 1376 | // testing a custom plugin which will pass along 1377 | // any messages received to it, but will inject 1378 | // the message "foo" at the beginning, and append 1379 | // the message "bar" after the last 1380 | ASQ2 = ASQ.clone(); 1381 | 1382 | ASQ2.extend("foobar",function(api,internals){ 1383 | return function __foobar__() { 1384 | api.then(function(done){ 1385 | // cheat and manually inject a message into 1386 | // the stream 1387 | internals("sequence_messages").push("foo"); 1388 | 1389 | // pass messages the proper way 1390 | done.apply(null, 1391 | ARRAY_SLICE.call(arguments,1) 1392 | .concat(["bar"]) 1393 | ); 1394 | }); 1395 | return api; 1396 | }; 1397 | }); 1398 | 1399 | ASQ2("Hello","World") 1400 | .foobar() // a custom plugin 1401 | .val(function(msg1,msg2,msg3,msg4){ 1402 | clearTimeout(timeout); 1403 | 1404 | if ( 1405 | arguments.length === 4 && 1406 | msg1 === "foo" && 1407 | msg2 === "Hello" && 1408 | msg3 === "World" && 1409 | msg4 === "bar" 1410 | ) { 1411 | PASS(testDone,label); 1412 | } 1413 | else { 1414 | var args = ARRAY_SLICE.call(arguments); 1415 | args.unshift(testDone,label); 1416 | FAIL.apply(FAIL,args); 1417 | } 1418 | }) 1419 | .or(function(){ 1420 | clearTimeout(timeout); 1421 | var args = ARRAY_SLICE.call(arguments); 1422 | args.unshift(testDone,label); 1423 | FAIL.apply(FAIL,args); 1424 | }); 1425 | 1426 | timeout = setTimeout(function(){ 1427 | FAIL(testDone,label + " (from timeout)"); 1428 | },2000); 1429 | }); 1430 | tests.push(function(testDone){ 1431 | var label = "Core Test #25", timeout; 1432 | 1433 | ASQ() 1434 | .then(function(done){ 1435 | var msgs = []; 1436 | 1437 | ASQ() 1438 | .gate( 1439 | function(done1){ 1440 | done1("hello"); 1441 | setTimeout(function(){ 1442 | done1("nope"); 1443 | },20); 1444 | }, 1445 | function(done2){ 1446 | setTimeout(function(){ 1447 | done2("world"); 1448 | },40); 1449 | setTimeout(function(){ 1450 | done2.fail("ouch"); 1451 | },60); 1452 | } 1453 | ) 1454 | .then( 1455 | function(){ 1456 | var args = ARRAY_SLICE.call(arguments,1); 1457 | msgs = msgs.concat(args); 1458 | }, 1459 | function(){ 1460 | var args = ARRAY_SLICE.call(arguments,1); 1461 | msgs = msgs.concat(args); 1462 | } 1463 | ) 1464 | .or(function(err){ 1465 | msgs.push(err); 1466 | }) 1467 | 1468 | ASQ( 1469 | function(done1){ 1470 | done1("42"); 1471 | setTimeout(function(){ 1472 | done1("boo"); 1473 | },80); 1474 | setTimeout(function(){ 1475 | done1.fail("oops"); 1476 | },100); 1477 | }, 1478 | function(){ 1479 | var args = ARRAY_SLICE.call(arguments,1); 1480 | msgs = msgs.concat(args); 1481 | }, 1482 | function(){ 1483 | var args = ARRAY_SLICE.call(arguments,1); 1484 | msgs = msgs.concat(args); 1485 | } 1486 | ) 1487 | .or(function(err){ 1488 | msgs.push(err); 1489 | }); 1490 | 1491 | setTimeout(function(){ 1492 | done.apply(ø,msgs); 1493 | },150); 1494 | }) 1495 | .val(function(msg1,msg2,msg3){ 1496 | if (!( 1497 | arguments.length === 3 && 1498 | msg1 === "42" && 1499 | msg2 === "hello" && 1500 | msg3 === "world" 1501 | )) { 1502 | clearTimeout(timeout); 1503 | var args = ARRAY_SLICE.call(arguments); 1504 | args.unshift(testDone,label); 1505 | FAIL.apply(FAIL,args); 1506 | } 1507 | }) 1508 | .then(function(){ 1509 | clearTimeout(timeout); 1510 | PASS(testDone,label); 1511 | }) 1512 | .onerror(function(){ 1513 | clearTimeout(timeout); 1514 | var args = ARRAY_SLICE.call(arguments); 1515 | args.unshift(testDone,label); 1516 | FAIL.apply(FAIL,args); 1517 | }); 1518 | 1519 | timeout = setTimeout(function(){ 1520 | FAIL(testDone,label + " (from timeout)"); 1521 | },2000); 1522 | }); 1523 | 1524 | return tests; 1525 | } 1526 | 1527 | return defineTests; 1528 | }); 1529 | --------------------------------------------------------------------------------