UNPKG

nicely

Version:

a minimalist asynchronous handler, inspired by _.after from underscore.

210 lines (197 loc) 6.21 kB
;(function(root) { var fnBind = Function.prototype.bind; var arraySlice = Array.prototype.slice; var bind = function(fn, context) { var args; if (fnBind && fn.bind) return fnBind.apply(fn, arraySlice.call(arguments, 1)); args = arraySlice.call(arguments, 2); return function bound() { return fn.apply(context, args.concat(arraySlice.call(arguments))); }; }; var defer = (function() { if (typeof process !== 'undefined') return bind(process.nextTick, process); return function(fn) { setTimeout(fn, 1); }; }()); var nicely = function nicely(times, callback) { if (typeof callback !== 'function') throw new TypeError('callback must be a function'); // whether an error has occured var errored = false; // the result parameters var params = {}; // create an anonymous function that sets the field return function next(field) { // handles a response // *must be called only once* return function after(err, result) { // ignore if errored or done if (errored || times <= 0) return; // callback with error if (err) return callback(errored = err, field); // set parameter params[field] = result; // call back if done if (--times === 0) callback(null, params); }; }; }; nicely.simply = function simply(times, callback) { if (typeof callback !== 'function') throw new TypeError('callback must be a function'); // whether an error has occurred var errored = false; // array of active tasks // handles a response // *must be called #times* return function next(err) { // ignore if errored or done if (errored || times <= 0) return; // callback with error if (err) return callback(errored = err); // call back if done if (--times === 0) callback(null); }; }; nicely.directly = function directly(times, callback) { if (typeof callback !== 'function') throw new TypeError('callback must be a function'); // whether an error has occured var errored = false; // the result parameters var params = new Array(times); // the ticker var ticker = 0; // handles a response // *must be called #times* return function next(err, result) { // ignore if errored or done if (errored || ticker >= times) return; // callback with error if (err) return callback(errored = err, ticker); // set parameter params[ticker++] = result; // call back if done if (ticker === times) callback(null, params); }; }; // sequentially fits with the theme, but is hard to type right nicely.sequentially = nicely.sequence = function sequentially(callback) { if (typeof callback !== 'function') throw new TypeError('callback must be a function'); // current done state var state = false; // the result parameters var params = {}; // the task queue var taskQueue = []; // handles the end of one task var endTask = function(name, err, result) { // just in case they called back and then threw if (state) return; if (err) { state = true; return callback.call(params, err, name); } params[name] = result; processTask(); }; // processes one task var processTask = function() { // fetch the next task var item = taskQueue.shift(); if (!item) { // we're done! state = true; return callback.call(params, null, params); } // make sure we don't allow uncaught exceptions, a common problem here try { item[0].apply(params, item[2]); } catch (err) { endTask(item[1], err); } }; // begins queue processing on (start of) next tick // should defer happen in processTask, for every call? defer(processTask); // queues another function return function queue(/*name, fn, args...*/) { var args = arraySlice.call(arguments); var name = args.shift(); args.push(bind(endTask, this, name)); taskQueue.push([args.shift(), name, args]); }; }; var intentlyDefaults = { times: 0, backoff: 2, initial: 100, maximum: 5000 // defer: false // abort: function(err) -> boolean }; // TODO: use different variables for callback and fn in different scores? // to avoid confusion. // TODO: need to make opts optional nicely.intently = function intently(opts, callback) { var callback = arguments[arguments.length - 1]; if (typeof callback !== 'function') throw new TypeError('callback must be a function'); opts = typeof opts === 'object' ? opts : {}; var options = {}; options.defer = !!opts.defer; options.abort = typeof opts.abort === 'function' ? opts.abort : null; for (var key in intentlyDefaults) options[key] = typeof opts[key] === 'number' ? opts[key] : intentlyDefaults[key]; return function begin(fn) { var calls = options.times || null; var delay = options.initial; var retry = function(err, result) { if (!err) return callback.apply(null, arguments); if ((calls !== null && --calls === 0) || (options.abort && options.abort(err))) return callback(err); setTimeout(boundFn, delay); delay = Math.min(delay * options.backoff, options.maximum); }; var fnArgs = arraySlice.call(arguments, 1); fnArgs.push(retry); var boundFn = bind.apply(root, [fn, root].concat(fnArgs)); if (options.defer) defer(boundFn); else fn.apply(root, fnArgs); }; }; nicely.version = nicely.VERSION = '0.1.4'; nicely._ = { nicely: nicely }; if (typeof exports !== 'undefined') { if (typeof module !== 'undefined') exports = module.exports = nicely; exports.nicely = nicely; } else { var previousNicely = root.nicely; root.nicely = nicely; nicely.noConflict = function() { root.nicely = previousNicely; }; } })(this);