1'use strict'; 2 3Object.defineProperty(exports, "__esModule", { 4 value: true 5}); 6exports.default = autoInject; 7 8var _auto = require('./auto.js'); 9 10var _auto2 = _interopRequireDefault(_auto); 11 12var _wrapAsync = require('./internal/wrapAsync.js'); 13 14var _wrapAsync2 = _interopRequireDefault(_wrapAsync); 15 16function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 17 18var FN_ARGS = /^(?:async\s+)?(?:function)?\s*\w*\s*\(\s*([^)]+)\s*\)(?:\s*{)/; 19var ARROW_FN_ARGS = /^(?:async\s+)?\(?\s*([^)=]+)\s*\)?(?:\s*=>)/; 20var FN_ARG_SPLIT = /,/; 21var FN_ARG = /(=.+)?(\s*)$/; 22 23function stripComments(string) { 24 let stripped = ''; 25 let index = 0; 26 let endBlockComment = string.indexOf('*/'); 27 while (index < string.length) { 28 if (string[index] === '/' && string[index + 1] === '/') { 29 // inline comment 30 let endIndex = string.indexOf('\n', index); 31 index = endIndex === -1 ? string.length : endIndex; 32 } else if (endBlockComment !== -1 && string[index] === '/' && string[index + 1] === '*') { 33 // block comment 34 let endIndex = string.indexOf('*/', index); 35 if (endIndex !== -1) { 36 index = endIndex + 2; 37 endBlockComment = string.indexOf('*/', index); 38 } else { 39 stripped += string[index]; 40 index++; 41 } 42 } else { 43 stripped += string[index]; 44 index++; 45 } 46 } 47 return stripped; 48} 49 50function parseParams(func) { 51 const src = stripComments(func.toString()); 52 let match = src.match(FN_ARGS); 53 if (!match) { 54 match = src.match(ARROW_FN_ARGS); 55 } 56 if (!match) throw new Error('could not parse args in autoInject\nSource:\n' + src); 57 let [, args] = match; 58 return args.replace(/\s/g, '').split(FN_ARG_SPLIT).map(arg => arg.replace(FN_ARG, '').trim()); 59} 60 61/** 62 * A dependency-injected version of the [async.auto]{@link module:ControlFlow.auto} function. Dependent 63 * tasks are specified as parameters to the function, after the usual callback 64 * parameter, with the parameter names matching the names of the tasks it 65 * depends on. This can provide even more readable task graphs which can be 66 * easier to maintain. 67 * 68 * If a final callback is specified, the task results are similarly injected, 69 * specified as named parameters after the initial error parameter. 70 * 71 * The autoInject function is purely syntactic sugar and its semantics are 72 * otherwise equivalent to [async.auto]{@link module:ControlFlow.auto}. 73 * 74 * @name autoInject 75 * @static 76 * @memberOf module:ControlFlow 77 * @method 78 * @see [async.auto]{@link module:ControlFlow.auto} 79 * @category Control Flow 80 * @param {Object} tasks - An object, each of whose properties is an {@link AsyncFunction} of 81 * the form 'func([dependencies...], callback). The object's key of a property 82 * serves as the name of the task defined by that property, i.e. can be used 83 * when specifying requirements for other tasks. 84 * * The `callback` parameter is a `callback(err, result)` which must be called 85 * when finished, passing an `error` (which can be `null`) and the result of 86 * the function's execution. The remaining parameters name other tasks on 87 * which the task is dependent, and the results from those tasks are the 88 * arguments of those parameters. 89 * @param {Function} [callback] - An optional callback which is called when all 90 * the tasks have been completed. It receives the `err` argument if any `tasks` 91 * pass an error to their callback, and a `results` object with any completed 92 * task results, similar to `auto`. 93 * @returns {Promise} a promise, if no callback is passed 94 * @example 95 * 96 * // The example from `auto` can be rewritten as follows: 97 * async.autoInject({ 98 * get_data: function(callback) { 99 * // async code to get some data 100 * callback(null, 'data', 'converted to array'); 101 * }, 102 * make_folder: function(callback) { 103 * // async code to create a directory to store a file in 104 * // this is run at the same time as getting the data 105 * callback(null, 'folder'); 106 * }, 107 * write_file: function(get_data, make_folder, callback) { 108 * // once there is some data and the directory exists, 109 * // write the data to a file in the directory 110 * callback(null, 'filename'); 111 * }, 112 * email_link: function(write_file, callback) { 113 * // once the file is written let's email a link to it... 114 * // write_file contains the filename returned by write_file. 115 * callback(null, {'file':write_file, 'email':'user@example.com'}); 116 * } 117 * }, function(err, results) { 118 * console.log('err = ', err); 119 * console.log('email_link = ', results.email_link); 120 * }); 121 * 122 * // If you are using a JS minifier that mangles parameter names, `autoInject` 123 * // will not work with plain functions, since the parameter names will be 124 * // collapsed to a single letter identifier. To work around this, you can 125 * // explicitly specify the names of the parameters your task function needs 126 * // in an array, similar to Angular.js dependency injection. 127 * 128 * // This still has an advantage over plain `auto`, since the results a task 129 * // depends on are still spread into arguments. 130 * async.autoInject({ 131 * //... 132 * write_file: ['get_data', 'make_folder', function(get_data, make_folder, callback) { 133 * callback(null, 'filename'); 134 * }], 135 * email_link: ['write_file', function(write_file, callback) { 136 * callback(null, {'file':write_file, 'email':'user@example.com'}); 137 * }] 138 * //... 139 * }, function(err, results) { 140 * console.log('err = ', err); 141 * console.log('email_link = ', results.email_link); 142 * }); 143 */ 144function autoInject(tasks, callback) { 145 var newTasks = {}; 146 147 Object.keys(tasks).forEach(key => { 148 var taskFn = tasks[key]; 149 var params; 150 var fnIsAsync = (0, _wrapAsync.isAsync)(taskFn); 151 var hasNoDeps = !fnIsAsync && taskFn.length === 1 || fnIsAsync && taskFn.length === 0; 152 153 if (Array.isArray(taskFn)) { 154 params = [...taskFn]; 155 taskFn = params.pop(); 156 157 newTasks[key] = params.concat(params.length > 0 ? newTask : taskFn); 158 } else if (hasNoDeps) { 159 // no dependencies, use the function as-is 160 newTasks[key] = taskFn; 161 } else { 162 params = parseParams(taskFn); 163 if (taskFn.length === 0 && !fnIsAsync && params.length === 0) { 164 throw new Error("autoInject task functions require explicit parameters."); 165 } 166 167 // remove callback param 168 if (!fnIsAsync) params.pop(); 169 170 newTasks[key] = params.concat(newTask); 171 } 172 173 function newTask(results, taskCb) { 174 var newArgs = params.map(name => results[name]); 175 newArgs.push(taskCb); 176 (0, _wrapAsync2.default)(taskFn)(...newArgs); 177 } 178 }); 179 180 return (0, _auto2.default)(newTasks, callback); 181} 182module.exports = exports['default'];