├── .npmignore ├── img.png ├── test ├── no-callback.js ├── empty-array.js ├── error.js └── basic.js ├── .airtap.yml ├── .travis.yml ├── LICENSE ├── package.json ├── index.js └── README.md /.npmignore: -------------------------------------------------------------------------------- 1 | .airtap.yml 2 | .travis.yml 3 | img.png 4 | test/ 5 | -------------------------------------------------------------------------------- /img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feross/run-auto/HEAD/img.png -------------------------------------------------------------------------------- /test/no-callback.js: -------------------------------------------------------------------------------- 1 | const auto = require('../') 2 | const test = require('tape') 3 | 4 | test('no callback', function (t) { 5 | t.plan(2) 6 | 7 | const tasks = { 8 | a: function (cb) { 9 | t.pass('cb 1') 10 | }, 11 | b: function (cb) { 12 | t.pass('cb 2') 13 | } 14 | } 15 | 16 | auto(tasks) 17 | }) 18 | -------------------------------------------------------------------------------- /.airtap.yml: -------------------------------------------------------------------------------- 1 | sauce_connect: true 2 | loopback: airtap.local 3 | browsers: 4 | - name: chrome 5 | version: latest 6 | - name: firefox 7 | version: latest 8 | - name: safari 9 | version: latest 10 | - name: microsoftedge 11 | version: latest 12 | - name: iphone 13 | version: latest 14 | - name: android 15 | version: latest 16 | -------------------------------------------------------------------------------- /test/empty-array.js: -------------------------------------------------------------------------------- 1 | const auto = require('../') 2 | const test = require('tape') 3 | 4 | test('empty tasks object', function (t) { 5 | t.plan(1) 6 | 7 | auto({}, function (err) { 8 | t.error(err) 9 | }) 10 | }) 11 | 12 | test('empty tasks object and no callback', function (t) { 13 | auto([]) 14 | t.pass('did not throw') 15 | t.end() 16 | }) 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - lts/* 4 | addons: 5 | sauce_connect: true 6 | hosts: 7 | - airtap.local 8 | env: 9 | global: 10 | - secure: iHaEA7KOZQjPmhaHtBh6Hbb9oyE/tHjNX6SfpGMLiqeLuI8f1eLP/ZYzXNfnBrk+ewW8PllTNAOeBFX7YEu8bShEMr4lTnoVxLruP0kjCs9a45y5DuEVbwbkK3bQrJEcpA/CSsPEwuzAd6tp4BqMs1EO3+bgJtF72I2pOfsvQrA= 11 | - secure: UsO8CCQphivglJ3gTfYEWcG/HSOucy11gH2KxBDnLE0NP9uOuEkpW21QWTEn9UWEwuQOA0Je/tJceoHmjTM2VowVHL06oBO0RguXlwzNXD3WUnQWQvPpE57y9VD1wkivrkBcntZMf6KrVsOhAABT7wmeYPXLSNmFx0DccptsJn8= 12 | -------------------------------------------------------------------------------- /test/error.js: -------------------------------------------------------------------------------- 1 | const auto = require('../') 2 | const test = require('tape') 3 | 4 | test('functions that return errors', function (t) { 5 | t.plan(2) 6 | 7 | const tasks = { 8 | a: function (cb) { 9 | t.pass('cb 1') 10 | cb(new Error('oops')) 11 | }, 12 | b: ['a', function (results, cb) { 13 | t.fail('cb 2 should not get called') 14 | }] 15 | } 16 | 17 | auto(tasks, function (err) { 18 | t.ok(err instanceof Error) 19 | }) 20 | }) 21 | 22 | test('auto error should pass partial results', function (t) { 23 | const tasks = { 24 | task1: function (cb) { 25 | cb(null, 'result1') 26 | }, 27 | task2: ['task1', function (results, cb) { 28 | cb(new Error('testerror'), 'result2') 29 | }], 30 | task3: ['task2', function (results, cb) { 31 | t.fail('task3 should not be called') 32 | }] 33 | } 34 | 35 | auto(tasks, function (err, results) { 36 | t.ok(err instanceof Error) 37 | t.equals(results.task1, 'result1') 38 | t.equals(results.task2, 'result2') 39 | t.end() 40 | }) 41 | }) 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Feross Aboukhadijeh 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /test/basic.js: -------------------------------------------------------------------------------- 1 | const auto = require('../') 2 | const test = require('tape') 3 | 4 | test('functions run in parallel', function (t) { 5 | const callOrder = [] 6 | 7 | const tasks = { 8 | task1: ['task2', function (results, cb) { 9 | setTimeout(function () { 10 | callOrder.push('task1') 11 | cb(null, 'res1') 12 | }, 25) 13 | }], 14 | task2: function (cb) { 15 | setTimeout(function () { 16 | callOrder.push('task2') 17 | cb(null, 'res2') 18 | }, 50) 19 | }, 20 | task3: ['task2', function (results, cb) { 21 | callOrder.push('task3') 22 | cb(null, 'res3') 23 | }], 24 | task4: ['task1', 'task2', function (results, cb) { 25 | callOrder.push('task4') 26 | cb(null, 'res4') 27 | }], 28 | task5: ['task2', function (results, cb) { 29 | setTimeout(function () { 30 | callOrder.push('task5') 31 | cb(null, 'res5') 32 | }, 0) 33 | }], 34 | task6: ['task2', function (results, cb) { 35 | callOrder.push('task6') 36 | cb(null, 'res6') 37 | }] 38 | } 39 | 40 | auto(tasks, function (err, results) { 41 | t.error(err) 42 | t.deepEqual(callOrder, ['task2', 'task6', 'task3', 'task5', 'task1', 'task4']) 43 | t.deepEqual(results, { 44 | task1: 'res1', 45 | task2: 'res2', 46 | task3: 'res3', 47 | task4: 'res4', 48 | task5: 'res5', 49 | task6: 'res6' 50 | }) 51 | t.end() 52 | }) 53 | }) 54 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "run-auto", 3 | "description": "Determine the best order for running async functions, and run them", 4 | "version": "2.0.4", 5 | "author": { 6 | "name": "Feross Aboukhadijeh", 7 | "email": "feross@feross.org", 8 | "url": "https://feross.org" 9 | }, 10 | "bugs": { 11 | "url": "https://github.com/feross/run-auto/issues" 12 | }, 13 | "dependencies": { 14 | "dezalgo": "^1.0.3" 15 | }, 16 | "devDependencies": { 17 | "airtap": "^3.0.0", 18 | "standard": "*", 19 | "tape": "^5.0.1" 20 | }, 21 | "homepage": "https://github.com/feross/run-auto", 22 | "keywords": [ 23 | "auto", 24 | "async", 25 | "function", 26 | "callback", 27 | "asynchronous", 28 | "run", 29 | "array", 30 | "run auto", 31 | "order", 32 | "async.auto" 33 | ], 34 | "license": "MIT", 35 | "main": "index.js", 36 | "repository": { 37 | "type": "git", 38 | "url": "git://github.com/feross/run-auto.git" 39 | }, 40 | "scripts": { 41 | "test": "standard && npm run test-node && npm run test-browser", 42 | "test-browser": "airtap -- test/*.js", 43 | "test-browser-local": "airtap --local -- test/*.js", 44 | "test-node": "tape test/*.js" 45 | }, 46 | "funding": [ 47 | { 48 | "type": "github", 49 | "url": "https://github.com/sponsors/feross" 50 | }, 51 | { 52 | "type": "patreon", 53 | "url": "https://www.patreon.com/feross" 54 | }, 55 | { 56 | "type": "consulting", 57 | "url": "https://feross.org/support" 58 | } 59 | ] 60 | } 61 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /*! run-auto. MIT License. Feross Aboukhadijeh */ 2 | module.exports = runAuto 3 | 4 | const dezalgo = require('dezalgo') 5 | 6 | const hasOwnProperty = Object.prototype.hasOwnProperty 7 | 8 | let _setImmediate 9 | if (typeof setImmediate === 'function') { 10 | _setImmediate = function (fn) { 11 | // not a direct alias for IE10+ compatibility 12 | setImmediate(fn) 13 | } 14 | } else { 15 | _setImmediate = function (fn) { 16 | setTimeout(fn, 0) 17 | } 18 | } 19 | 20 | function runAuto (tasks, cb) { 21 | if (cb) cb = dezalgo(cb) 22 | const results = {} 23 | const listeners = [] 24 | 25 | const keys = Object.keys(tasks) 26 | let remainingTasks = keys.length 27 | 28 | if (!remainingTasks) { 29 | return cb && cb(null, results) 30 | } 31 | 32 | function addListener (fn) { 33 | listeners.unshift(fn) 34 | } 35 | 36 | function removeListener (fn) { 37 | for (let i = 0; i < listeners.length; i += 1) { 38 | if (listeners[i] === fn) { 39 | listeners.splice(i, 1) 40 | return 41 | } 42 | } 43 | } 44 | 45 | function taskComplete () { 46 | remainingTasks -= 1 47 | listeners.slice(0).forEach(function (fn) { 48 | fn() 49 | }) 50 | } 51 | 52 | addListener(function () { 53 | if (!remainingTasks && cb) { 54 | const thecb = cb 55 | // prevent final cb from calling itself if it errors 56 | cb = null 57 | thecb(null, results) 58 | } 59 | }) 60 | 61 | keys.forEach(function (key) { 62 | const task = Array.isArray(tasks[key]) 63 | ? tasks[key] 64 | : [tasks[key]] 65 | const requires = task.slice(0, task.length - 1) 66 | 67 | const taskcb = function (err) { 68 | let args = Array.prototype.slice.call(arguments, 1) 69 | if (args.length <= 1) { 70 | args = args[0] 71 | } 72 | 73 | if (err) { 74 | const safeResults = {} 75 | Object.keys(results).forEach(function (rkey) { 76 | safeResults[rkey] = results[rkey] 77 | }) 78 | safeResults[key] = args 79 | if (cb) cb(err, safeResults) 80 | // stop subsequent errors hitting cb multiple times 81 | cb = null 82 | } else { 83 | results[key] = args 84 | _setImmediate(taskComplete) 85 | } 86 | } 87 | 88 | const ready = function () { 89 | return requires.reduce(function (a, x) { 90 | return a && hasOwnProperty.call(results, x) 91 | }, true) && !hasOwnProperty.call(results, key) 92 | } 93 | 94 | if (ready()) { 95 | if (requires.length === 0) { 96 | task[task.length - 1](taskcb) 97 | } else { 98 | task[task.length - 1](results, taskcb) 99 | } 100 | } else { 101 | const listener = function () { 102 | if (ready()) { 103 | removeListener(listener) 104 | if (requires.length === 0) { 105 | task[task.length - 1](taskcb) 106 | } else { 107 | task[task.length - 1](results, taskcb) 108 | } 109 | } 110 | } 111 | addListener(listener) 112 | } 113 | }) 114 | } 115 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # run-auto [![travis][travis-image]][travis-url] [![npm][npm-image]][npm-url] [![downloads][downloads-image]][downloads-url] [![javascript style guide][standard-image]][standard-url] 2 | 3 | [travis-image]: https://img.shields.io/travis/feross/run-auto/master.svg 4 | [travis-url]: https://travis-ci.org/feross/run-auto 5 | [npm-image]: https://img.shields.io/npm/v/run-auto.svg 6 | [npm-url]: https://npmjs.org/package/run-auto 7 | [downloads-image]: https://img.shields.io/npm/dm/run-auto.svg 8 | [downloads-url]: https://npmjs.org/package/run-auto 9 | [standard-image]: https://img.shields.io/badge/code_style-standard-brightgreen.svg 10 | [standard-url]: https://standardjs.com 11 | 12 | #### Determine the best order for running async functions, ***LIKE MAGIC!*** 13 | 14 | ![auto](https://raw.githubusercontent.com/feross/run-auto/master/img.png) [![Sauce Test Status](https://saucelabs.com/browser-matrix/run-auto.svg)](https://saucelabs.com/u/run-auto) 15 | 16 | ### install 17 | 18 | ``` 19 | npm install run-auto 20 | ``` 21 | 22 | ### usage 23 | 24 | #### auto(tasks, [callback]) 25 | 26 | Determines the best order for running the functions in `tasks`, based on their 27 | requirements. Each function can optionally depend on other functions being completed 28 | first, and each function is run as soon as its requirements are satisfied. 29 | 30 | If any of the functions pass an error to their callback, the `auto` sequence will 31 | stop. Further tasks will not execute (so any other functions depending on it will 32 | not run), and the main `callback` is immediately called with the error. 33 | 34 | Functions also receive an object containing the results of functions which have 35 | completed so far as the first argument, if they have dependencies. If a task 36 | function has no dependencies, it will only be passed a callback. 37 | 38 | ##### arguments 39 | 40 | - `tasks` - An object. Each of its properties is either a function or an array of requirements, with the function itself the last item in the array. The object's key of a property serves as the name of the task defined by that property, i.e. can be used when specifying requirements for other tasks. The function receives one or two arguments: 41 | - a `results` object, containing the results of the previously executed functions, only passed if the task has any dependencies, **Argument order changed in 2.0** 42 | - a `callback(err, result)` function, which must be called when finished, passing an `error` (which can be `null`) and the result of the function's execution. **Argument order changed in 2.0** 43 | - `callback(err, results)` - An optional callback which is called when all the tasks have been completed. It receives the `err` argument if any `tasks` pass an error to their callback. Results are always returned; however, if an error occurs, no further `tasks` will be performed, and the results object will only contain partial results. 44 | 45 | ##### example 46 | 47 | ```js 48 | var auto = require('run-auto') 49 | 50 | auto({ 51 | getData: function (callback) { 52 | console.log('in getData') 53 | // async code to get some data 54 | callback(null, 'data', 'converted to array') 55 | }, 56 | makeFolder: function (callback) { 57 | console.log('in makeFolder') 58 | // async code to create a directory to store a file in 59 | // this is run at the same time as getting the data 60 | callback(null, 'folder') 61 | }, 62 | writeFile: ['getData', 'makeFolder', function (results, callback) { 63 | console.log('in writeFile', JSON.stringify(results)) 64 | // once there is some data and the directory exists, 65 | // write the data to a file in the directory 66 | callback(null, 'filename') 67 | }], 68 | emailLink: ['writeFile', function (results, callback) { 69 | console.log('in emailLink', JSON.stringify(results)) 70 | // once the file is written let's email a link to it... 71 | // results.writeFile contains the filename returned by writeFile. 72 | callback(null, { file: results.writeFile, email: 'user@example.com' }) 73 | }] 74 | }, function(err, results) { 75 | console.log('err = ', err) 76 | console.log('results = ', results) 77 | }) 78 | ``` 79 | 80 | #### usage note 81 | 82 | Note, all functions are called with a `results` object as a second argument, so it is 83 | unsafe to pass functions in the` tasks` object which cannot handle the extra argument. 84 | 85 | For example, this snippet of code: 86 | 87 | ```js 88 | auto({ 89 | readData: async.apply(fs.readFile, 'data.txt', 'utf-8') 90 | }, callback) 91 | ``` 92 | 93 | will have the effect of calling `readFile` with the results object as the last argument, 94 | which will fail, like this: 95 | 96 | ```js 97 | fs.readFile('data.txt', 'utf-8', cb, {}) 98 | ``` 99 | 100 | Instead, wrap the call to `readFile` in a function which does not forward the `results` 101 | object: 102 | 103 | ```js 104 | auto({ 105 | readData: function (cb, results) { 106 | fs.readFile('data.txt', 'utf-8', cb) 107 | } 108 | }, callback) 109 | ``` 110 | 111 | This module is basically equavalent to 112 | [`async.auto`](https://github.com/caolan/async#autotasks-callback), but it's 113 | handy to just have the one function you need instead of the kitchen sink. Modularity! 114 | Especially handy if you're serving to the browser and need to reduce your javascript 115 | bundle size. 116 | 117 | Works great in the browser with [browserify](http://browserify.org/)! 118 | 119 | ### see also 120 | 121 | - [run-parallel](https://github.com/feross/run-parallel) 122 | - [run-parallel-limit](https://github.com/feross/run-parallel-limit) 123 | - [run-series](https://github.com/feross/run-series) 124 | - [run-waterfall](https://github.com/feross/run-waterfall) 125 | 126 | ### license 127 | 128 | MIT. Copyright (c) [Feross Aboukhadijeh](http://feross.org). 129 | 130 | Image credit: Wizard Hat designed by Andrew Fortnum 131 | --------------------------------------------------------------------------------