UNPKG

q-plus

Version:

A Q add-on that adds many promise-based flow-controlling utilities.

267 lines (242 loc) 7.74 kB
/*! * Q library (c) 2009-2012 Kris Kowal under the terms of the MIT * license found at http://github.com/kriskowal/q/raw/master/LICENSE * Inspired by async (https://github.com/caolan/async) */ (function (definition) { // CommonJS if (typeof exports === "object" && typeof module === "object") { module.exports = definition(require('q')); // RequireJS } else if (typeof define === "function" && define.amd) { define(definition); // <script> } else if (typeof self.Q !== "undefined") { self.Q = definition(self.Q); } else { throw new Error("Environment not recognized."); } })(function(Q) { var setMethods = function(q) { var fn = q.makePromise.prototype; fn.each = each; fn.map = map; fn.eachSeries = eachSeries; fn.forEach = eachSeries; fn.mapSeries = mapSeries; fn.while = whileFn; fn.until = untilFn; fn.times = times; fn.timesSeries = timesSeries; q.while = function(test, fn) { return this().while(test, fn); }; q.until = function(test, fn) { return this().until(test, fn); }; q.times = function(n, fn) { return this().times(n, fn); }; q.timesSeries = function(n, fn) { return this().timesSeries(n, fn); }; }; setMethods(Q); var newQ = Q; /* // Goal: Q+ = Q, Q+(Q) = Q // Problem: newQ doesn't carry over Q methods, so Q.delay wouldn't work var newQ = function(x) { // Use provided Q if (x && x.defer && x.reject) { Q = x; x = 'f3b642dc'; } // Set methods id it hasn't been done if (!Q.makePromise.prototype.eachSeries) { setMethods(Q); } // Return the Q library or a promise if (x === 'f3b642dc') return Q; return Q(x); }; */ // Reduce function that accepts Arrays & Plain Objects function reduce(arr, fn, accu) { if (typeof arr === 'object' && !Array.isArray(arr)) { for (var key in arr) { accu = fn(accu, arr[key], key); } } else { for(var i = 0; i < arr.length; i++) { accu = fn(accu, arr[i]); } } return accu; } /** * Executes function for each element of an array or object, * running all functions or promises in parallel. * @promise {(array|object)} * @param {function} fn : The function called per iteration * @param value : Value of element * @param key|index : Key if object, index if array * @returns {object} Original object */ function each(fn) { var i = 0; return this.then(function(object) { return Q.all(reduce(object, function(array, value, key) { var fnv = Q.isPromise(value) ? value.then(function(v) { return fn(v, key || i++); }) : fn(value, key || i++); return array.concat(fnv); }, [])) .thenResolve(object); }); }; /** * Transforms an array or object into a new array using the iterator * function, running all functions or promises in parallel. * @promise {(array|object)} * @param {function} fn : The function called per iteration * @param value : Value of element * @param [key] : Key of element * @returns {array} Transformed array */ function map(fn) { // Allow promise as iterator //fn = Q.promised(fn); return this.then(function(object) { return Q.all(reduce(object, function(array, value, key) { var fnv = Q.isPromise(value) ? value.then(function(v) { return fn(v, key); }) : fn(value, key); return array.concat(fnv); }, [])); }); }; /** * Executes function for each element of an array or object, running * any promises in series only after the last has been completed. * @promise {(array|object)} * @param {function} fn : The function called per iteration * @param value : Value of element * @param key|index : Key if object, index if array * @returns {object} Original object */ function eachSeries(fn) { var i = 0; return this.then(function(object) { return reduce(object, function(newPromise, value, key) { // Allow value to be a promise if (Q.isPromise(value)) return newPromise.then(function() { return value; }).then(function(v) { return fn(v, key || i++); }); return newPromise.then(function() { return fn(value, key || i++);; }); }, Q()) .thenResolve(object) }); } /** * Transforms an array or object into a new array using the iterator function, * running any promises in series only after the last has been completed. * @promise {(array|object)} * @param {function} fn : The function called per iteration * @param value : Value of element * @param [key] : Key of element * @returns {array} Transformed array */ function mapSeries(fn) { var newArray = []; // Allow iterator return to be a promise function push(value, key) { value = fn(value, key); if (Q.isPromise(value)) return value.then(function(v) { newArray.push(v); }); newArray.push(value); } return this.then(function(object) { return reduce(object, function(newPromise, value, key) { // Allow value to be a promise if (Q.isPromise(value)) return newPromise.then(function() { return value; }).then(function(v) { return push(v, key); }); return newPromise.then(function() { return push(value, key) }); }, Q()); }).thenResolve(newArray); }; /** * Repeatedly call a function while a test function returns true. * @promise {*} last * @param {function} test : synchronous truth test * @param value : Value from last iteration * @param {function} fn : The function called per iteration * @param value : Value from last iteration * @returns {*} Value from last iteration */ function whileFn(test, fn) { return this.then(function(last) { if (test(last)) { return Q(fn(last)).then(function(value) { return Q(value).while(test, fn); }); } return last; }); }; /** * Repeatedly call a function while a test function returns false. * Same options as #while */ function untilFn(test, fn) { return this.then(function(last) { if (!test(last)) { return Q(fn(last)).then(function(value) { return Q(value).until(test, fn); }); } return last; }); }; /** * Calls the callback function n times, and accumulates results in the same manner you would use with map. * @promise {*} last * @param {number} n : How many times to iterate * @param {function} fn : The function called per iteration * @param value : Last return value * @returns {array} New array */ function times(n, fn) { var counter = []; return this.then(function(last) { for (var i = 0; i < n; i++) { counter.push(last || i); } return Q(counter).map(fn); }); }; /** * Calls the callback function n times, and accumulates results in the same manner you would use with map. * Same as #times */ function timesSeries(n, fn) { var counter = []; return this.then(function(last) { for (var i = 0; i < n; i++) { counter.push(last || i); } return Q(counter).mapSeries(fn); }); }; return newQ; });