diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..98a4353 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,18 @@ +# ╔═╗╔╦╗╦╔╦╗╔═╗╦═╗┌─┐┌─┐┌┐┌┌─┐┬┌─┐ +# ║╣ ║║║ ║ ║ ║╠╦╝│ │ ││││├┤ ││ ┬ +# o╚═╝═╩╝╩ ╩ ╚═╝╩╚═└─┘└─┘┘└┘└ ┴└─┘ +# +# This file (`.editorconfig`) exists to help maintain consistent formatting +# throughout this package, the Sails framework, and the Node-Machine project. +# +# To review what each of these options mean, see: +# http://editorconfig.org/ +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/.gitignore b/.gitignore index 69e7c6d..1bd4d97 100644 --- a/.gitignore +++ b/.gitignore @@ -1,23 +1,46 @@ -lib-cov +# ┌─┐┬┌┬┐╦╔═╗╔╗╔╔═╗╦═╗╔═╗ +# │ ┬│ │ ║║ ╦║║║║ ║╠╦╝║╣ +# o└─┘┴ ┴ ╩╚═╝╝╚╝╚═╝╩╚═╚═╝ +# +# This file (`.gitignore`) exists to signify to `git` that certain files +# and/or directories should be ignored for the purposes of version control. +# +# This is primarily useful for excluding temporary files of all sorts; stuff +# generated by IDEs, build scripts, automated tests, package managers, or even +# end-users (e.g. file uploads). `.gitignore` files like this also do a nice job +# at keeping sensitive credentials and personal data out of version control systems. +# + +############################ +# sails / node.js / npm +############################ +node_modules +npm-debug.log +.node_history + +############################ +# editor & OS files +############################ +*.swo +*.swp +*.swn +*.swm *.seed *.log -*.csv -*.dat *.out *.pid -*.gz - -pids -logs -results - -npm-debug.log - -.\#* -*# -node_modules -ssl +lib-cov .DS_STORE +*# +*\# +.\#* *~ .idea -nbproject \ No newline at end of file +.netbeans +nbproject + +############################ +# misc +############################ +.tmp +dump.rdb diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 0000000..f485e2e --- /dev/null +++ b/.jshintrc @@ -0,0 +1,130 @@ +{ + // ┬┌─┐╦ ╦╦╔╗╔╔╦╗┬─┐┌─┐ + // │└─┐╠═╣║║║║ ║ ├┬┘│ + // o└┘└─┘╩ ╩╩╝╚╝ ╩ ┴└─└─┘ + // + // This file (`.jshintrc`) exists to help with consistency of code + // throughout this package, and throughout Sails and the Node-Machine project. + // + // To review what each of these options mean, see: + // http://jshint.com/docs/options + // + // (or: https://github.com/jshint/jshint/blob/master/examples/.jshintrc) + + + + ////////////////////////////////////////////////////////////////////// + // NOT SUPPORTED IN SOME JSHINT VERSIONS SO LEAVING COMMENTED OUT: + ////////////////////////////////////////////////////////////////////// + // Prevent overwriting prototypes of native classes like `Array`. + // (doing this is _never_ ok in any of our packages that are intended + // to be used as dependencies of other developers' modules and apps) + // "freeze": true, + ////////////////////////////////////////////////////////////////////// + + + ////////////////////////////////////////////////////////////////////// + // EVERYTHING ELSE: + ////////////////////////////////////////////////////////////////////// + + // Allow the use of `eval` and `new Function()` + // (we sometimes actually need to use these things) + "evil": true, + + // Tolerate funny-looking dashes in RegExp literals. + // (see https://github.com/jshint/jshint/issues/159#issue-903547) + "regexdash": true, + + // The potential runtime "Environments" (as defined by jshint) + // that the _style_ of code written in this package should be + // compatible with (not the code itself, of course). + "browser": true, + "node": true, + "wsh": true, + + // Tolerate the use `[]` notation when dot notation would be possible. + // (this is sometimes preferable for readability) + "sub": true, + + // Do NOT suppress warnings about mixed tabs and spaces + // (two spaces always, please; see `.editorconfig`) + "smarttabs": false, + + // Suppress warnings about trailing whitespace + // (this is already enforced by the .editorconfig, so no need to warn as well) + "trailing": false, + + // Suppress warnings about the use of expressions where fn calls or assignments + // are expected, and about using assignments where conditionals are expected. + // (while generally a good idea, without this setting, JSHint needlessly lights up warnings + // in existing, working code that really shouldn't be tampered with. Pandora's box and all.) + "expr": true, + "boss": true, + + // Do NOT suppress warnings about using functions inside loops + // (in the general case, we should be using iteratee functions with `_.each()` + // or `Array.prototype.forEach()` instead of `for` or `while` statements + // anyway. This warning serves as a helpful reminder.) + "loopfunc": false, + + // Suppress warnings about "weird constructions" + // i.e. allow code like: + // ``` + // (new (function OneTimeUsePrototype () { } )) + // ``` + // + // (sometimes order of operations in JavaScript can be scary. There is + // nothing wrong with using an extra set of parantheses when the mood + // strikes or you get "that special feeling".) + "supernew": true, + + // Do NOT allow backwards, node-dependency-style commas. + // (while this code style choice was used by the project in the past, + // we have since standardized these practices to make code easier to + // read, albeit a bit less exciting) + "laxcomma": false, + + // Do NOT allow avant garde use of commas in conditional statements. + // (this prevents accidentally writing code like: + // ``` + // if (!_.contains(['+ci', '-ci', '∆ci', '+ce', '-ce', '∆ce']), change.verb) {...} + // ``` + // See the problem in that code? Neither did we-- that's the problem!) + "nocomma": true, + + // Strictly enforce the consistent use of single quotes. + // (this is a convention that was established primarily to make it easier + // to grep [or FIND+REPLACE in Sublime] particular string literals in + // JavaScript [.js] files. Note that JSON [.json] files are, of course, + // still written exclusively using double quotes around key names and + // around string literals.) + "quotmark": "single", + + // Do NOT suppress warnings about the use of `==null` comparisons. + // (please be explicit-- use Lodash or `require('util')` and call + // either `.isNull()` or `.isUndefined()`) + "eqnull": false, + + // Strictly enforce the use of curly braces with `if`, `else`, and `switch` + // as well as, much less commonly, `for` and `while` statements. + // (this is just so that all of our code is consistent, and to avoid bugs) + "curly": true, + + // Strictly enforce the use of `===` and `!==`. + // (this is always a good idea. Check out "Truth, Equality, and JavaScript" + // by Angus Croll [the author of "If Hemmingway Wrote JavaScript"] for more + // explanation as to why.) + "eqeqeq": true, + + // Allow initializing variables to `undefined`. + // For more information, see: + // • https://jslinterrors.com/it-is-not-necessary-to-initialize-a-to-undefined + // • https://github.com/jshint/jshint/issues/1484 + // + // (it is often very helpful to explicitly clarify the initial value of + // a local variable-- especially for folks new to more advanced JavaScript + // and who might not recognize the subtle, yet critically important differences between our seemingly + // between `null` and `undefined`, and the impact on `typeof` checks) + "-W080": true + +} diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..7f802e7 --- /dev/null +++ b/.npmignore @@ -0,0 +1,34 @@ +.git +./.gitignore +./.jshintrc +./.editorconfig +./.travis.yml +./appveyor.yml +./example +./examples +./test +./tests +./.github + +node_modules +npm-debug.log +.node_history +*.swo +*.swp +*.swn +*.swm +*.seed +*.log +*.out +*.pid +lib-cov +.DS_STORE +*# +*\# +.\#* +*~ +.idea +.netbeans +nbproject +.tmp +dump.rdb diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..fb60c2d --- /dev/null +++ b/.travis.yml @@ -0,0 +1,29 @@ +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# ╔╦╗╦═╗╔═╗╦ ╦╦╔═╗ ┬ ┬┌┬┐┬ # +# ║ ╠╦╝╠═╣╚╗╔╝║╚═╗ └┬┘││││ # +# o ╩ ╩╚═╩ ╩ ╚╝ ╩╚═╝o ┴ ┴ ┴┴─┘ # +# # +# This file configures Travis CI. # +# (i.e. how we run the tests... mainly) # +# # +# https://docs.travis-ci.com/user/customizing-the-build # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # + +language: node_js + +node_js: + - "0.10" + - "0.12" + - "4" + - "5" + - "6" + - "7" + - "node" + +branches: + only: + - master + +notifications: + email: + - ci@sailsjs.com diff --git a/README.md b/README.md index 1a3e885..6d71f61 100644 --- a/README.md +++ b/README.md @@ -102,4 +102,3 @@ var user = $$(User).find(17); ``` -[![githalytics.com alpha](https://cruel-carlota.pagodabox.com/c87267eee787dd508d0106260261b6dc "githalytics.com")](http://githalytics.com/mikermcneil/parley) diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..bc5ee85 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,47 @@ +# # # # # # # # # # # # # # # # # # # # # # # # # # +# ╔═╗╔═╗╔═╗╦ ╦╔═╗╦ ╦╔═╗╦═╗ ┬ ┬┌┬┐┬ # +# ╠═╣╠═╝╠═╝╚╗╔╝║╣ ╚╦╝║ ║╠╦╝ └┬┘││││ # +# ╩ ╩╩ ╩ ╚╝ ╚═╝ ╩ ╚═╝╩╚═o ┴ ┴ ┴┴─┘ # +# # +# This file configures Appveyor CI. # +# (i.e. how we run the tests on Windows) # +# # +# https://www.appveyor.com/docs/lang/nodejs-iojs/ # +# # # # # # # # # # # # # # # # # # # # # # # # # # + + +# Test against these versions of Node.js. +environment: + matrix: + - nodejs_version: "0.10" + - nodejs_version: "0.12" + - nodejs_version: "4" + - nodejs_version: "5" + - nodejs_version: "6" + - nodejs_version: "7" + +# Install scripts. (runs after repo cloning) +install: + # Get the latest stable version of Node.js + # (Not sure what this is for, it's just in Appveyor's example.) + - ps: Install-Product node $env:nodejs_version + # Install declared dependencies + - npm install + + +# Post-install test scripts. +test_script: + # Output Node and NPM version info. + # (Presumably just in case Appveyor decides to try any funny business? + # But seriously, always good to audit this kind of stuff for debugging.) + - node --version + - npm --version + # Run the actual tests. + - npm test + + +# Don't actually build. +# (Not sure what this is for, it's just in Appveyor's example. +# I'm not sure what we're not building... but I'm OK with not +# building it. I guess.) +build: off diff --git a/index.js b/index.js index f09f312..ebc8d08 100644 --- a/index.js +++ b/index.js @@ -2,287 +2,286 @@ // parley.js // A bare-bones framework for serial flow control // -// NOTE: +// NOTE: // parley is very good at simple, serial async logic. // It does a great job of making it easy to avoid callback nesting. // However, the more conditionals your asynchronous logic has, // the more parley starts to suck. // -// For more complex flow control, I highly recommnd +// For more complex flow control, I highly recommnd // the async framework by Caolan McMahon: // (https://github.com/caolan/async) // //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// (function() { - var async = require('async'); - var _ = require('underscore'); - - var Parley = module.exports = function() { - var parley = this; - - // Whether this parley has been called at all yet - parley.virgin = true; - - // Parse dependencies from arguments - parley.dependencies = _.toArray(arguments) || []; - - // The execution queue for this parley instance - parley.xQ = []; - - // The discard stack - parley.dStack = []; - - // Event delegation - parley.subscribers = []; - parley.subscribe = function(otherParley, cb) { - otherParley.subscribers.push(cb); - }; - parley.signal = function(err, data) { - _.each(parley.subscribers, function(subscriberCb) { - if(subscriberCb) subscriberCb(err, data); - }); - }; - - // A function to receive actual function for flow control - // and delay execution until the queue is cleared - var flowControl = function(fn, ctx) { - var isObject = _.isObject(fn); - var isFn = _.isFunction(fn); - - // If the function is actually an object, return a copy of the object - // with all of its functions transformed into deferred functions - if (isObject && !isFn) { - var obj = _.clone(fn); - for (var key in obj) { - var value = obj[key]; - if (_.isFunction(value)) { - obj[key] = receiveArgumentsAndShift(parley,value,ctx); - } - } - return obj; - } - // If this is a normal function, - else if (isFn) { - return receiveArgumentsAndShift(parley,fn,ctx); - } - else { - console.warn("parley received an invalid object as a parameter:", fn); - throw new Error ("parley received an invalid object as a parameter!"); - } - }; - - // Add utility methods - flowControl.log = function (msg) { - if (msg && msg._isParleyCallback) return flowControl(function (err,data,cb) { - doLog(err, "ERROR"); - doLog(data,"DATA"); - cb(); - }) (msg); - else return flowControl(function (msg,cb) { - doLog(msg); - cb(); - }) (msg); - - function doLog (msg, type) { - if (type) console.log("$$: ",msg,"("+type+")"); - else console.log("$$: ",msg); - } - }; - - // Save reference to subscribers - flowControl.subscribers = parley.subscribers; - return flowControl; - }; - - - - - // A function which receives and assigns arguments for the current fn - // It will also kick off the next function if necessary - function receiveArgumentsAndShift(parley,fn,ctx) { - - var startFn = queueActionObjectWithConditions({}); - - // Add if* functionality to startFn - startFn.ifError = function () { - return queueActionObjectWithConditions({ifError: true}).apply(ctx,arguments); - }; - startFn.ifNull = function () { - return queueActionObjectWithConditions({ifNull: true}).apply(ctx,arguments); - }; - startFn.ifNotNull = function () { - return queueActionObjectWithConditions({ifNotNull: true}).apply(ctx,arguments); - }; - - - // Return startFn without any conditions set - return startFn; - - - - - // Return fn that appends runFunction to execution queue - function queueActionObjectWithConditions(conditions) { - return function () { - var args = _.toArray(arguments); - - var actionObject = { - _isParleyCallback: true, - fn: runFunction, - args: args, - ctx: ctx, - error: null, - data: null, - conditions: conditions - }; - parley.xQ.unshift(actionObject); - manageQueue(); - - // Return the action objet - return actionObject; - }; - } - - - // Closure which manages queue and dependencies - function manageQueue() { - // If this is the first call, and all dependencies are met, go ahead and start the parley - if(parley.virgin) { - parley.virgin = false; - - // Wait until all dependencies have finished before shifting the queue initially - if(parley.dependencies) { - async.forEach(parley.dependencies, function(item, cb) { - parley.subscribe(item, cb); - }, shiftQueue); - } else shiftQueue(); - } - } - - - // A callback function that is fired when the function is complete - // After each call, peek at execution queue and save error and result data - // At the end of the execution queue, fire a `done()` event, if one exists - function cb(err, data) { - var lastAction = peek(parley.dStack); - lastAction.error = err; - lastAction.data = data; - - if (parley.xQ.length > 0) shiftQueue(); - else parley.signal(err, data); - } - - // Shift an action out of the execution queue and onto the discard stack - function shiftQueue() { - if (parley.xQ.length === 0) throw new Error("Nothing left in queue!"); - var action = parley.xQ.pop(); - parley.dStack.push(action); - - // Transform arguments - var args = action.args; - - // Only run action if conditions pass - var conditionsPassed = _.all(_.keys(action.conditions), function (condition) { - var lastAction; - if (!action.conditions[condition]) return false; - else if (condition === 'ifError') { - args = expandArgsWithLastAction(args); - args = expandParleyActionObject(args); - return !!args[0]; - } - else if (condition === 'ifNull') { - args = expandArgsWithLastAction(args); - args = expandParleyActionObject(args); - return _.isNull(args[1]); - } - else if (condition === 'ifNotNull') { - args = expandArgsWithLastAction(args); - args = expandParleyActionObject(args); - return !_.isNull(args[1]); - } - else return true; - }); - - // Trigger, shift queue, or signal as necessary - if (conditionsPassed) { - action.fn.apply(action.ctx, args); - } - else if (parley.xQ.length > 0) shiftQueue.apply(ctx,action.args); - else parley.signal(args[0], args[1]); - } - - // If no args were provided, use the top of the discard stack, expanded - function expandArgsWithLastAction(args) { - if (args.length === 0) { - lastAction = parley.dStack[0]; - if (lastAction) { - args[0] = lastAction.error; - args[1] = lastAction.data; - } - } - return args; - } - - // Wrapper for actual function call - // Receives original arguments as parameters - function runFunction () { - var args = _.toArray(arguments); - - // If only arg is a parley callback object, - // convert into classic (err,data) notation - args = expandParleyActionObject(args); - - // Add callback as argument - args.push(cb); - - // Add reference to discard stack as final argument - args.push(parley.dStack); - - - // Defer until the event loop finishes to avoid triggering before all functions have been added - _.defer(function () { - // Run function in proper context w/ proper arguments - // (if ctx is null, the fn will run in the global execution context) - fn.apply(ctx, args); - }); - } - - // Convert [parleyActionObject] into [err,data] - function expandParleyActionObject(origArgs) { - var args = _.clone(origArgs); - - // If only a parley action object was provided, expand it - if (args.length === 1 && args[0] && args[0]._isParleyCallback) { - var cbError = args[0].error; - var cbData = args[0].data; - args[0] = cbError; - args[1] = cbData; - } - return args; - } - } - - // Peek at upcoming item in queue - function peek(arr) { - return arr[arr.length - 1]; - } - - // Add deferred log function for logging parley callback - Parley.log = function () { - var args = _.toArray(arguments); - if (args.length < 3 || !_.isFunction(args[2])) { - console.warn("Invalid arguments passed to Parley.log:",args); - return args.pop()(); - } - - console.log('Parley.log :: ',args); - return args.pop()(); - - // Write error or data and trigger callback - // if (args[0]) console.error(args[0]); - // else console.log(args[1]); - // args.pop()(); - }; - -})(); \ No newline at end of file + var async = require('async'); + var _ = require('lodash'); + + var Parley = module.exports = function() { + var parley = this; + + // Whether this parley has been called at all yet + parley.virgin = true; + + // Parse dependencies from arguments + parley.dependencies = _.toArray(arguments) || []; + + // The execution queue for this parley instance + parley.xQ = []; + + // The discard stack + parley.dStack = []; + + // Event delegation + parley.subscribers = []; + parley.subscribe = function(otherParley, cb) { + otherParley.subscribers.push(cb); + }; + parley.signal = function(err, data) { + _.each(parley.subscribers, function(subscriberCb) { + if (subscriberCb) subscriberCb(err, data); + }); + }; + + // A function to receive actual function for flow control + // and delay execution until the queue is cleared + var flowControl = function(fn, ctx) { + var isObject = _.isObject(fn); + var isFn = _.isFunction(fn); + + // If the function is actually an object, return a copy of the object + // with all of its functions transformed into deferred functions + if (isObject && !isFn) { + var obj = _.clone(fn); + for (var key in obj) { + var value = obj[key]; + if (_.isFunction(value)) { + obj[key] = receiveArgumentsAndShift(parley, value, ctx); + } + } + return obj; + } + // If this is a normal function, + else if (isFn) { + return receiveArgumentsAndShift(parley, fn, ctx); + } else { + console.warn("parley received an invalid object as a parameter:", fn); + throw new Error("parley received an invalid object as a parameter!"); + } + }; + + // Add utility methods + flowControl.log = function(msg) { + if (msg && msg._isParleyCallback) return flowControl(function(err, data, cb) { + doLog(err, "ERROR"); + doLog(data, "DATA"); + cb(); + })(msg); + else return flowControl(function(msg, cb) { + doLog(msg); + cb(); + })(msg); + + function doLog(msg, type) { + if (type) console.log("$$: ", msg, "(" + type + ")"); + else console.log("$$: ", msg); + } + }; + + // Save reference to subscribers + flowControl.subscribers = parley.subscribers; + return flowControl; + }; + + + + // A function which receives and assigns arguments for the current fn + // It will also kick off the next function if necessary + function receiveArgumentsAndShift(parley, fn, ctx) { + + var startFn = queueActionObjectWithConditions({}); + + // Add if* functionality to startFn + startFn.ifError = function() { + return queueActionObjectWithConditions({ + ifError: true + }).apply(ctx, arguments); + }; + startFn.ifNull = function() { + return queueActionObjectWithConditions({ + ifNull: true + }).apply(ctx, arguments); + }; + startFn.ifNotNull = function() { + return queueActionObjectWithConditions({ + ifNotNull: true + }).apply(ctx, arguments); + }; + + + // Return startFn without any conditions set + return startFn; + + + + // Return fn that appends runFunction to execution queue + function queueActionObjectWithConditions(conditions) { + return function() { + var args = _.toArray(arguments); + + var actionObject = { + _isParleyCallback: true, + fn: runFunction, + args: args, + ctx: ctx, + error: null, + data: null, + conditions: conditions + }; + parley.xQ.unshift(actionObject); + manageQueue(); + + // Return the action objet + return actionObject; + }; + } + + + // Closure which manages queue and dependencies + function manageQueue() { + // If this is the first call, and all dependencies are met, go ahead and start the parley + if (parley.virgin) { + parley.virgin = false; + + // Wait until all dependencies have finished before shifting the queue initially + if (parley.dependencies) { + async.forEach(parley.dependencies, function(item, cb) { + parley.subscribe(item, cb); + }, shiftQueue); + } else shiftQueue(); + } + } + + + // A callback function that is fired when the function is complete + // After each call, peek at execution queue and save error and result data + // At the end of the execution queue, fire a `done()` event, if one exists + function cb(err, data) { + var lastAction = peek(parley.dStack); + lastAction.error = err; + lastAction.data = data; + + if (parley.xQ.length > 0) shiftQueue(); + else parley.signal(err, data); + } + + // Shift an action out of the execution queue and onto the discard stack + function shiftQueue() { + if (parley.xQ.length === 0) throw new Error("Nothing left in queue!"); + var action = parley.xQ.pop(); + parley.dStack.push(action); + + // Transform arguments + var args = action.args; + + // Only run action if conditions pass + var conditionsPassed = _.all(_.keys(action.conditions), function(condition) { + var lastAction; + if (!action.conditions[condition]) return false; + else if (condition === 'ifError') { + args = expandArgsWithLastAction(args); + args = expandParleyActionObject(args); + return !!args[0]; + } else if (condition === 'ifNull') { + args = expandArgsWithLastAction(args); + args = expandParleyActionObject(args); + return _.isNull(args[1]); + } else if (condition === 'ifNotNull') { + args = expandArgsWithLastAction(args); + args = expandParleyActionObject(args); + return !_.isNull(args[1]); + } else return true; + }); + + // Trigger, shift queue, or signal as necessary + if (conditionsPassed) { + action.fn.apply(action.ctx, args); + } else if (parley.xQ.length > 0) shiftQueue.apply(ctx, action.args); + else parley.signal(args[0], args[1]); + } + + // If no args were provided, use the top of the discard stack, expanded + function expandArgsWithLastAction(args) { + if (args.length === 0) { + lastAction = parley.dStack[0]; + if (lastAction) { + args[0] = lastAction.error; + args[1] = lastAction.data; + } + } + return args; + } + + // Wrapper for actual function call + // Receives original arguments as parameters + function runFunction() { + var args = _.toArray(arguments); + + // If only arg is a parley callback object, + // convert into classic (err,data) notation + args = expandParleyActionObject(args); + + // Add callback as argument + args.push(cb); + + // Add reference to discard stack as final argument + args.push(parley.dStack); + + + // Defer until the event loop finishes to avoid triggering before all functions have been added + _.defer(function() { + // Run function in proper context w/ proper arguments + // (if ctx is null, the fn will run in the global execution context) + fn.apply(ctx, args); + }); + } + + // Convert [parleyActionObject] into [err,data] + function expandParleyActionObject(origArgs) { + var args = _.clone(origArgs); + + // If only a parley action object was provided, expand it + if (args.length === 1 && args[0] && args[0]._isParleyCallback) { + var cbError = args[0].error; + var cbData = args[0].data; + args[0] = cbError; + args[1] = cbData; + } + return args; + } + } + + // Peek at upcoming item in queue + function peek(arr) { + return arr[arr.length - 1]; + } + + // Add deferred log function for logging parley callback + Parley.log = function() { + var args = _.toArray(arguments); + if (args.length < 3 || !_.isFunction(args[2])) { + console.warn("Invalid arguments passed to Parley.log:", args); + return args.pop()(); + } + + console.log('Parley.log :: ', args); + return args.pop()(); + + // Write error or data and trigger callback + // if (args[0]) console.error(args[0]); + // else console.log(args[1]); + // args.pop()(); + }; + +})(); diff --git a/package.json b/package.json index 037d2df..2a66ad3 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "Convention-over-configuration flow control", "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "node ./node_modules/mocha/bin/mocha test" }, "repository": { "type": "git", @@ -13,13 +13,11 @@ "keywords": [ "flowcontrol", "async", - "promise" + "promise", + "deferred" ], "author": "Mike McNeil", "license": "MIT", - "readmeFilename": "README.md", "dependencies": { - "underscore": "~1.4.2", - "async": "~0.1.22" } } diff --git a/parleyTest.js b/parleyTest.js index 45f9b66..c0c84af 100644 --- a/parleyTest.js +++ b/parleyTest.js @@ -1,253 +1,254 @@ // Dependencies var async = require('async'); -var _ = require('underscore'); +var _ = require('lodash'); var parley = require('./index.js'); function runTests() { - function z(str, cb) { - console.log(str); - setTimeout(function() { - cb(null, str); - }, 1000); - } - - function zzz($$promise, cb) { - console.log("Got result:", $$promise.data); - cb(); - } - - // Define deferred parley objects - var $$ = new parley(); - // var $$1 = new parley(); - // var $$2 = new parley($$, $$1); - - ////////////////////////////////////////////////////////////////////// - // Basic usage - ////////////////////////////////////////////////////////////////////// - // console.log("\n\n\n\n\n\n\n"); - // console.log("*************"); - // console.log("Test #1"); - // console.log("*************"); - // $$ (z) ('test'); // z('test') - // $$ (z) ('2'); // z(2) - // var $$result = $$ (z) ('3'); // result = {data: z(3)} - // $$1 (z) ('some other chain'); // z('some other chain') - // $$1 (z) ('some other chain #2'); // z('some other chain #2') - // $$2 (zzz) ($$result); // zzz(result) - - ////////////////////////////////////////////////////////////////////// - // Testing Parley.log and deferred object generation - ////////////////////////////////////////////////////////////////////// - // console.log("\n\n\n\n\n\n\n"); - // console.log("*************"); - // console.log("Test #2"); - // console.log("*************"); - // var $$User = $$(User); - // var $$log = $$(parley.log); - // $$log($$User.find(3)); // This will return a data object - // $$log($$User.find("Johnny")); // This will return an error - - - ////////////////////////////////////////////////////////////////////// - // Real world use case of some simple serial logic - ////////////////////////////////////////////////////////////////////// - // console.log("\n\n\n\n\n\n\n"); - // console.log("*************"); - // console.log("Test #3"); - // console.log("*************"); - - // var cb = function(err, data, cb) { - // console.log("\n\n***\nOperation complete!"); - // if (err) console.warn(err); - // else console.log(data); - // console.log("***\n"); - // }; - - // var $$cb = $$(cb); - // var martin = $$(User).find(3); - // $$cb(martin); - - ////////////////////////////////////////////////////////////////////// - // Serial callbacks - ////////////////////////////////////////////////////////////////////// - // function test4 (cb) { - // console.log("\n\n\n\n\n\n\n"); - // console.log("*************"); - // console.log("Test #4"); - // console.log("*************"); - - // // Callbacks can be executed serially - // // BTW the closure can access asynchronous data, but not consistently. Always pass data as an arg.) - // var $ = new parley(); - // var user3 = $(User).find(3); - // $(cb)(user3); - // // user3 = $(function (err,data,cb) { - // // if (err) cb(err); - // // else cb(err,data); - // // })(user3); - // // $(cb)(user3); - // } - - // ////////////////////////////////////////////////////////////////////// - // // An example of how to manage conditionals - // ////////////////////////////////////////////////////////////////////// - - // function test5 (cb) { - // console.log("\n\n\n\n\n\n\n"); - // console.log("*************"); - // console.log("Test #5"); - // console.log("*************"); - - // var $ = new parley (); - // var criteria = {name: 'widget'}; - - // // Rather than using multiple serial parley calls, - // // define your logic within a single block and use nested parley objects if necessary - // var someUser = $(User).find(criteria); - // someUser = $(function (err,data,cb) { - // var $ = new parley(); - // if (err) cb(err); - // else if (data) cb(err,data); - // else { - // var newUser = $(User).create(criteria); - // $(cb)(newUser); - // } - // })(someUser); - // $(cb)(someUser); - // } - - - ////////////////////////////////////////////////////////////////////// - // An example of how to use built-in support for asynchonous conditions - ////////////////////////////////////////////////////////////////////// - function test6 (cb) { - console.log("\n\n\n\n\n\n\n"); - console.log("*************"); - console.log("Test #6"); - console.log("*************"); - - var $ = new parley (); - var criteria = 35; - var someUser = $(User).find(criteria); - - - // ifNull conditional shortcut fn - $(cb).ifNull(); - - // Anything afterwards is treated as "else" - $(cb)(null,"notNull!"); - } - - - - ////////////////////////////////////////////////////////////////////// - // An example of building a transactional findAndCreate() function - ////////////////////////////////////////////////////////////////////// - function test7 (cb) { - console.log("\n\n\n\n\n\n\n"); - console.log("*************"); - console.log("Test #7"); - console.log("*************"); - - var $ = new parley(); - - var u = $(findAndCreate)({name: 'mike'}); - $(cb)(u); - - function findAndCreate(criteria,cb) { - var $ = new parley (); - $(User).lock(criteria); - $(User).find(criteria); - $(cb).ifNotNull(); - $(cb).ifError("Error encountered when trying to find User."); - var u = $(User).create(criteria); - $(User).unlock(criteria); - $(cb)(u); - } - } - - ////////////////////////////////////////////////////////////////////// - // An example of using $$.log - ////////////////////////////////////////////////////////////////////// - function test8 (cb) { - console.log("\n\n\n\n\n\n\n"); - console.log("*************"); - console.log("Test #8"); - console.log("*************"); - - var $$ = new parley(); - $$.log("test"); - $$(cb)(); - } - - - - // Run tests - var $ = new parley(); - var result; - - // result = $(test4)(); - // $(function (err,data,cb) { - // console.log("Outcome:\t("+err+",",data,")"); - // cb(); - // })(result); - - // result = $(test5)(); - // $(function (err,data,cb) { - // console.log("Outcome:\t("+err+",",data,")"); - // cb(); - // })(result); - - // result = $(test6)(); - // $(function (err,data,cb) { - // console.log("Outcome:\t("+err+",",data,")"); - // cb(); - // })(result); - - result = $(test7)(); - $(function (err,data,cb) { - console.log("Outcome:\t("+err+",",data,")"); - cb(); - })(result); - result = $(test8)(); - - - $(function (cb) { - console.log("done"); - - // Not required, but it's good to clean up after yourself - cb(); - })(); - - // var findOrCreate = function (criteria,cb) { - // var $ = new parley(); - - // $(User).lock(criteria); - - // var user = $(User).find(criteria); - // $(function (err,data) { - - // }); - - // $(User).create(criteria); - // $(function (err,data) { - - // }); - - // $(User).unlock(criteria); - // $(cb)(user); - // }; - - // var def = { - // name: 'johnny' - // }; - // var johnny = $$(findOrCreate) (def); - // $$(cb)(johnny); + function z(str, cb) { + console.log(str); + setTimeout(function() { + cb(null, str); + }, 1000); + } + + function zzz($$promise, cb) { + console.log("Got result:", $$promise.data); + cb(); + } + + // Define deferred parley objects + var $$ = new parley(); + // var $$1 = new parley(); + // var $$2 = new parley($$, $$1); + + ////////////////////////////////////////////////////////////////////// + // Basic usage + ////////////////////////////////////////////////////////////////////// + // console.log("\n\n\n\n\n\n\n"); + // console.log("*************"); + // console.log("Test #1"); + // console.log("*************"); + // $$ (z) ('test'); // z('test') + // $$ (z) ('2'); // z(2) + // var $$result = $$ (z) ('3'); // result = {data: z(3)} + // $$1 (z) ('some other chain'); // z('some other chain') + // $$1 (z) ('some other chain #2'); // z('some other chain #2') + // $$2 (zzz) ($$result); // zzz(result) + + ////////////////////////////////////////////////////////////////////// + // Testing Parley.log and deferred object generation + ////////////////////////////////////////////////////////////////////// + // console.log("\n\n\n\n\n\n\n"); + // console.log("*************"); + // console.log("Test #2"); + // console.log("*************"); + // var $$User = $$(User); + // var $$log = $$(parley.log); + // $$log($$User.find(3)); // This will return a data object + // $$log($$User.find("Johnny")); // This will return an error + + + ////////////////////////////////////////////////////////////////////// + // Real world use case of some simple serial logic + ////////////////////////////////////////////////////////////////////// + // console.log("\n\n\n\n\n\n\n"); + // console.log("*************"); + // console.log("Test #3"); + // console.log("*************"); + + // var cb = function(err, data, cb) { + // console.log("\n\n***\nOperation complete!"); + // if (err) console.warn(err); + // else console.log(data); + // console.log("***\n"); + // }; + + // var $$cb = $$(cb); + // var martin = $$(User).find(3); + // $$cb(martin); + + ////////////////////////////////////////////////////////////////////// + // Serial callbacks + ////////////////////////////////////////////////////////////////////// + // function test4 (cb) { + // console.log("\n\n\n\n\n\n\n"); + // console.log("*************"); + // console.log("Test #4"); + // console.log("*************"); + + // // Callbacks can be executed serially + // // BTW the closure can access asynchronous data, but not consistently. Always pass data as an arg.) + // var $ = new parley(); + // var user3 = $(User).find(3); + // $(cb)(user3); + // // user3 = $(function (err,data,cb) { + // // if (err) cb(err); + // // else cb(err,data); + // // })(user3); + // // $(cb)(user3); + // } + + // ////////////////////////////////////////////////////////////////////// + // // An example of how to manage conditionals + // ////////////////////////////////////////////////////////////////////// + + // function test5 (cb) { + // console.log("\n\n\n\n\n\n\n"); + // console.log("*************"); + // console.log("Test #5"); + // console.log("*************"); + + // var $ = new parley (); + // var criteria = {name: 'widget'}; + + // // Rather than using multiple serial parley calls, + // // define your logic within a single block and use nested parley objects if necessary + // var someUser = $(User).find(criteria); + // someUser = $(function (err,data,cb) { + // var $ = new parley(); + // if (err) cb(err); + // else if (data) cb(err,data); + // else { + // var newUser = $(User).create(criteria); + // $(cb)(newUser); + // } + // })(someUser); + // $(cb)(someUser); + // } + + + ////////////////////////////////////////////////////////////////////// + // An example of how to use built-in support for asynchonous conditions + ////////////////////////////////////////////////////////////////////// + function test6(cb) { + console.log("\n\n\n\n\n\n\n"); + console.log("*************"); + console.log("Test #6"); + console.log("*************"); + + var $ = new parley(); + var criteria = 35; + var someUser = $(User).find(criteria); + + + // ifNull conditional shortcut fn + $(cb).ifNull(); + + // Anything afterwards is treated as "else" + $(cb)(null, "notNull!"); + } + + + + ////////////////////////////////////////////////////////////////////// + // An example of building a transactional findAndCreate() function + ////////////////////////////////////////////////////////////////////// + function test7(cb) { + console.log("\n\n\n\n\n\n\n"); + console.log("*************"); + console.log("Test #7"); + console.log("*************"); + + var $ = new parley(); + + var u = $(findAndCreate)({ + name: 'mike' + }); + $(cb)(u); + + function findAndCreate(criteria, cb) { + var $ = new parley(); + $(User).lock(criteria); + $(User).find(criteria); + $(cb).ifNotNull(); + $(cb).ifError("Error encountered when trying to find User."); + var u = $(User).create(criteria); + $(User).unlock(criteria); + $(cb)(u); + } + } + + ////////////////////////////////////////////////////////////////////// + // An example of using $$.log + ////////////////////////////////////////////////////////////////////// + function test8(cb) { + console.log("\n\n\n\n\n\n\n"); + console.log("*************"); + console.log("Test #8"); + console.log("*************"); + + var $$ = new parley(); + $$.log("test"); + $$(cb)(); + } + + + + // Run tests + var $ = new parley(); + var result; + + // result = $(test4)(); + // $(function (err,data,cb) { + // console.log("Outcome:\t("+err+",",data,")"); + // cb(); + // })(result); + + // result = $(test5)(); + // $(function (err,data,cb) { + // console.log("Outcome:\t("+err+",",data,")"); + // cb(); + // })(result); + + // result = $(test6)(); + // $(function (err,data,cb) { + // console.log("Outcome:\t("+err+",",data,")"); + // cb(); + // })(result); + + result = $(test7)(); + $(function(err, data, cb) { + console.log("Outcome:\t(" + err + ",", data, ")"); + cb(); + })(result); + result = $(test8)(); -} + $(function(cb) { + console.log("done"); + + // Not required, but it's good to clean up after yourself + cb(); + })(); + + // var findOrCreate = function (criteria,cb) { + // var $ = new parley(); + + // $(User).lock(criteria); + + // var user = $(User).find(criteria); + // $(function (err,data) { + + // }); + // $(User).create(criteria); + // $(function (err,data) { + + // }); + + // $(User).unlock(criteria); + // $(cb)(user); + // }; + + // var def = { + // name: 'johnny' + // }; + // var johnny = $$(findOrCreate) (def); + // $$(cb)(johnny); + + +} @@ -261,47 +262,47 @@ function runTests() { // Define User object for real world use case above -var User = { - // Random other parameters will remain untouched - a: 1, - b: "g248", - c: "g8q234", - - // Mock find function - find: function(criteria, cb) { - setTimeout(function() { - console.log("Finding users...",criteria); - if(_.isObject(criteria) || _.isFinite(criteria)) { - var result = criteria === 3 ? { - name: "Martin" - } : null; - cb(null, result); - } else cb("Invalid criteria parameter (" + criteria + ") passed to User.find."); - }, 1050); - }, - create: function(definition, cb) { - console.log("Creating User..."); - setTimeout(function() { - if(_.isObject(definition)) { - console.log("User created!"); - cb(null, definition); - } else cb("Invalid definition parameter (" + definition + ") passed to User.create."); - }, 150); - }, - - lock: function (criteria,cb) { - setTimeout(function () { - console.log("Locking users...",criteria); - cb(); - },500); - }, - - unlock: function (criteria,cb) { - setTimeout(function () { - console.log("Unlocking users...",criteria); - cb(); - },500); - } +var User = { + // Random other parameters will remain untouched + a: 1, + b: "g248", + c: "g8q234", + + // Mock find function + find: function(criteria, cb) { + setTimeout(function() { + console.log("Finding users...", criteria); + if (_.isObject(criteria) || _.isFinite(criteria)) { + var result = criteria === 3 ? { + name: "Martin" + } : null; + cb(null, result); + } else cb("Invalid criteria parameter (" + criteria + ") passed to User.find."); + }, 1050); + }, + create: function(definition, cb) { + console.log("Creating User..."); + setTimeout(function() { + if (_.isObject(definition)) { + console.log("User created!"); + cb(null, definition); + } else cb("Invalid definition parameter (" + definition + ") passed to User.create."); + }, 150); + }, + + lock: function(criteria, cb) { + setTimeout(function() { + console.log("Locking users...", criteria); + cb(); + }, 500); + }, + + unlock: function(criteria, cb) { + setTimeout(function() { + console.log("Unlocking users...", criteria); + cb(); + }, 500); + } }; @@ -389,4 +390,4 @@ runTests(); // if ($$.error) throw $$.error; // console.log("DATA:",$$.data); // cb(); -// }) ($_result); \ No newline at end of file +// }) ($_result); diff --git a/test/basic.test.js b/test/basic.test.js index b6c8ce7..169c3dc 100644 --- a/test/basic.test.js +++ b/test/basic.test.js @@ -9,16 +9,15 @@ // // Dependencies var _ = require('underscore'); var parley = require('../index'); -var assert = require("assert"); describe('parley', function() { - describe('#parley object ', function() { - it('should be created w/o an error', function(done) { - var $ = new parley(); - $(function (cb) { - done(); - })(); - }); - }); -}); \ No newline at end of file + describe('#parley object ', function() { + it('should be created w/o an error', function(done) { + var $ = new parley(); + $(function(cb) { + done(); + })(); + }); + }); +});