/** * An instruction is a self contained piece of information that can be created and then later be executed. {@link Run} * instance uses this as the values of the `Run.next` queue. * * @module Run~Instructions */ var _ = require('lodash'), Timings = require('./timings'), arrayProtoSlice = Array.prototype.slice, arrayProtoUnshift = Array.prototype.unshift, pool; // function /** * Create a new instruction pool * * @param {Object.} processors - hash of all command processor functions * @returns {InstructionPool} */ pool = function (processors) { !_.isObject(processors) && (processors = {}); /** * Create a new instruction to be executed later * * @constructor * * @param {String} name - name of the instruction. this is useful for later lookup of the `processor` function when * deserialising this object * @param {Object} [payload] - a **JSON compatible** object that will be forwarded as the 2nd last parameter to the * processor. * @param {Array} [args] - all the arguments that needs to be passed to the processor is in this array * @private * @example * var inst = Instruction.create(function (arg1, payload, next) { * console.log(payload); * next(null, 'hello-on-execute with ' + arg1); * }, 'sample-instruction', { * payloadData1: 'value' * }, ['one-arg']); * * // now, when we do execute, the result will be a console.log of payload and message will be as expected * instance.execute(function (err, message) { * console.log(message); * }); * */ var Instruction = function (name, payload, args) { var processor = processors[name]; if (!_.isString(name) || !_.isFunction(processor)) { throw new Error('run-instruction: invalid construction'); } // ensure that payload is an object so that data storage can be done. also ensure arguments is an array !_.isObject(payload) && (payload = {}); !_.isArray(args) && (args = []); _.assign(this, /** @lends Instruction.prototype */ { /** * @type {String} */ action: name, /** * @type {Object} */ payload: payload, /** * @type {Array} */ in: args, /** * @type {Timings} */ timings: Timings.create(), /** * @private * @type {Function} */ _processor: processor }); // record the timing when this instruction was created this.timings.record('created'); }; /** * Shortcut to `new Instruction(...);` * * @param {Function} processor * @param {String} name * @param {Object} [payload] * @param {Array} [args] * * @returns {Instruction} */ Instruction.create = function (processor, name, payload, args) { return new Instruction(processor, name, payload, args); }; /** * Store all thenable items * * @type {Array} */ Instruction._queue = []; /** * Executes an instruction with previously saved payload and arguments * * @param {Function} callback * @param {*} [scope] * * @todo: use timeback and control it via options sent during pool creation as an option */ Instruction.prototype.execute = function (callback, scope) { !scope && (scope = this); var params = _.clone(this.in), sealed = false, doneAndSpread = function (err) { if (sealed) { console.error('__postmanruntime_fatal_debug: instruction.execute callback called twice'); if (err) { console.error(err); } return; } sealed = true; this.timings.record('end'); var args = arrayProtoSlice.call(arguments); arrayProtoUnshift.call(args, scope); if (err) { // in case it errored, we do not process any thenables _.isArray(this._catch) && _.invokeMap(this._catch, _.apply, scope, arguments); } else { // call all the `then` stuff and then the main callback _.isArray(this._done) && _.invokeMap(this._done, _.apply, scope, _.tail(arguments)); } setTimeout(callback.bind.apply(callback, args), 0); }.bind(this); // add two additional arguments at the end of the arguments saved - i.e. the payload and a function to call the // callback asynchronously params.push(this.payload, doneAndSpread); this.timings.record('start'); // run the processor in a try block to avoid causing stalled runs try { this._processor.apply(scope, params); } catch (e) { doneAndSpread(e); } }; Instruction.prototype.done = function (callback) { (this._done || (this._done = [])).push(callback); return this; }; Instruction.prototype.catch = function (callback) { (this._catch || (this._catch = [])).push(callback); return this; }; Instruction.clear = function () { _.forEach(Instruction._queue, function (instruction) { delete instruction._done; }); Instruction._queue.length = 0; }; Instruction.shift = function () { return Instruction._queue.shift.apply(Instruction._queue, arguments); }; Instruction.unshift = function () { return Instruction._queue.unshift.apply(Instruction._queue, arguments); }; Instruction.push = function () { return Instruction._queue.push.apply(Instruction._queue, arguments); }; return Instruction; }; module.exports = { pool: pool };