# Introduction

[TaskGroup](https://github.com/bevry/taskgroup) provides two classes, `Task` and `TaskGroup`

## Tasks

Tasks are used to wrap a function (both synchronous and asynchronous functions are supported) inside a task execution flow.

This is useful as a consistent interface for executing tasks and doing something on their completion or failure, as well as catching uncaught errors and handling them safely.

We can define a synchronous task like so:

```javascript
var Task = require('taskgroup').Task

// Create a task for our synchronous function
var task = new Task(function () {
    // Do something ...
    return "a synchronous result"

    // You can also return an error
    // return new Error("something went wrong")
})

// Add our completion callback for once the task has completed
task.done(function (err, result) {
    // Do something now that the task has completed ...
    console.log([err, result])
    /* [null, "a sychronous result"] */
})

// Execute the task
task.run()
```

And an asynchronous task like so:

```javascript
var Task = require('taskgroup').Task

// Create a task for our synchronous function
var task = new Task(function (complete) {
    // Do something asynchronous
    setTimeout(function () {
        // the error is the first callback argument, and the results the following arguments
        return complete(null, "an asychronous result")

        // So to provide an error instead, you would just pass over the first callback argument
        // return complete("something went wrong")
    }, 5000)  // execute the timeout after 5 seconds
})

// Add our completion callback for once the task has completed
task.done(function (err, result) {
    // Do something now that the task has completed ...
    console.log([err, result])
    /* [null, "an asychronous result"] */
})

// Execute the task
task.run()
```

## TaskGroup

Often at times, we want to execute multiple things and wait for the completion. TaskGroup makes this easy with the other class, `TaskGroup`.

We simply create a `TaskGroup` and add our Tasks to it!

```javascript
var TaskGroup = require('taskgroup').TaskGroup

// Create our serial task group
var tasks = new TaskGroup({storeResult: true})

// Add an asynchronous task to it
tasks.addTask(function (complete) {
    setTimeout(function () {
        return complete(null, "a result")
    }, 5000)  // execute the timeout after 5 seconds
})

// Add a synchronous task to it
tasks.addTask(function () {
    return "a synchronous result"
})

// Add our completion callback for once the tasks have completed
tasks.done(function (err, results) {
    console.log([err, results])
    /* [null, [
        [null, "an asychronous result"],
        [null, "a sychronous result"]
    ]] */
})

// Execute the task group
tasks.run()
```

> The `storeResult` configuration property is new to TaskGroup v5. By default, TaskGroup v5 does not store results, whereas TaskGroup v4 did. Without `storeResult: true` the `results` parameter on the `done` completion callback would be undefined. In the examples here we wish to store the results such that the order execution becomes obvious, normally you may or may not want to use `storeResult`.

Now by default, the TaskGroup will execute serially. This means that each task will execute one by one, waiting for the previous task to complete before moving on to the next task. This can also be considered having a concurrency of `1`. This is called *serial* execution.

If we wanted to execute say two tasks at a time we could want a concurrency of `2`, or three tasks at a time, a concurrency of `3` would be set, or unlimited tasks at a time, a concurrency of `0` would be set.

We can customise the concurrency of the task group by passing it over as a configuration option, either via the TaskGroup constructor or via the `setConfig` method. Let's see what this would look like if we were do a concurrency of `0`. This is called *parallel* execution.

```javascript
var TaskGroup = require('taskgroup').TaskGroup

// Create our parallel task group
var tasks = new TaskGroup({storeResult: true, concurrency: 0})

// Add an asynchronous task to it
tasks.addTask(function (complete) {
    setTimeout(function () {
        return complete(null, "a result")
    }, 5000)  // execute the timeout after 5 seconds
})

// Add a synchronous task to it
tasks.addTask(function () {
    return "a synchronous result"
})

// Add our completion callback for once the tasks have completed
tasks.done(function (err, results) {
    console.log([err, results])
    /* [null, [
        [null, "a sychronous result"],
        [null, "an asychronous result"]
    ]] */
})

// Execute the task group
tasks.run()
```

Notice how the groups results are now in a different order. This occured because with parallel execution, we didn't have to wait for the asynchronous function to complete its 5 second delay before executing and completing the second function (the synchronous one).

You can mix and match as many functions as you want with TaskGroups.

### Nested TaskGroups

You can also nest TaskGroups inside TaskGroups.

A common use case for this is when you would like a portion of your tasks to execute in parallel, and portion of your tasks to execute in serial.

Such a use case would look like so:

```javascript
var TaskGroup = require('taskgroup').TaskGroup

// Create our serial task group
var tasks = new TaskGroup({storeResult: true})

// Add the first serial task
tasks.addTask(function () {
    return "first serial task"
})

// Add a nested group of tasks that you would like executed in parallel
tasks.addGroup(function (addGroup, addTask) {
    // Set this nested group to execute in parallel
    this.setConfig({concurrency: 0})

    // Add an asynchronous task to the nested group
    addTask(function (complete) {
        setTimeout(function () {
            return complete(null, "a result")
        }, 5000)  // execute the timeout after 5 seconds
    })

    // Add a synchronous task to the nested group
    addTask(function () {
        return "a synchronous result"
    })
})

// Add the second serial task
tasks.addTask(function () {
    return "second serial task"
})

// Add our completion callback for once the tasks have completed
tasks.done(function (err, results) {
    console.log([err, results])
    /* [null, [
        [null, "first serial task"],
        [null, [
            [null, "a sychronous result"],
            [null, "an asychronous result"]
        ]],
        [null, "second serial task"]
    ]] */
})

// Execute the task group
tasks.run()
```

## Handling Errors

Safely handling errors is an important thing to do. TaskGroup makes this easy by safely catching any errors that your task may throw, isolating the destruction to the task alone, and providing to the task or taskgroup's completion callback.

When an error is detected, the remaining tasks in a TaskGroup will be cleared, and the TaskGroup's completion callback with the error will be fired. If you wish to not abort on error, you can set `abortOnError: false`. [More configuration options.](http://rawgit.com/bevry/taskgroup/master/docs/#TaskGroup#setConfig)

```javascript
var TaskGroup = require('taskgroup').TaskGroup

// Create our serial task group
var tasks = new TaskGroup({storeResult: true})

// Add an asynchronous task to the TaskGroup
tasks.addTask(function (complete) {
    setTimeout(function () {
        return complete(new Error("the first task failed"))
    }, 5000)  // execute the timeout after 5 second
})

// Add a synchronous task to the TaskGroup
tasks.addTask(function () {
    return "the second task"
})

// Add our completion callback for once the tasks have completed
tasks.done(function (err, results) {
    console.log([err, results])
    /* [Error("the first task failed"), [
        [Error("the first task failed")]
    ]] */
})

// Execute the task group
tasks.run()
```

Which comes in very handy when dealing with asynchronous parallel code:

```javascript
var TaskGroup = require('taskgroup').TaskGroup

// Create our parallel task group
var tasks = new TaskGroup({soreResult: true, concurrency: 0})

// Add an asynchronous task to the TaskGroup
tasks.addTask(function (complete) {
    setTimeout(function () {
        return complete("the first task failed")
    }, 5000)  // execute the timeout after 5 seconds
})

// Add an asynchronous task to the TaskGroup
tasks.addTask(function (complete) {
    setTimeout(function () {
        return complete("the second task failed")
    }, 1000)  // execute the timeout after 1 seconds
})

// Add our completion callback for once the tasks have completed
tasks.done(function (err, results) {
    console.log([err, results])
    /* [Error("the second task failed"), [
        [Error("the second task failed")],
        [Error("the first task failed")]
    ]] */
})

// Execute the task group
tasks.run()
```

Now even though the first task's completion callback still fires, it is successfully ignored, as the TaskGroup has exited.

## Notes

### **Promise Style Mistakes**

A common mistake for people coming from the complex land of promises, is that they may make code like this:

```javascript
// Execute the task
task.run()

// Add our completion callback for once the task has completed
task.done(function (err, results) {
    // Do something now that the task has completed ...
    console.log([err, results])
    /* [null, "a sychronous result"] */
})
```

Expecting the completion callback to fire right away. However, as the TaskGroup is just an event emitter, the completion listener is only fired at the point in time when the `complete` event is emitted. As such, you should always add your completion listener before you run your task or taskgroup, never after.

### Legacy Environments

In Node v0.8 and browser environments, TaskGroup may not be able to catch all thrown errors due to the lack of usable [domains](http://nodejs.org/docs/v0.10.35/api/domain.html) in those environments (domains only became usable in Node v0.10.0 and above).

To help ensure errors are caught in all environments, be sure to always follow the [best practices for error handling](http://stackoverflow.com/a/7313005), regardless of your environment.

## Graduation

Now you know all the essentials to getting started with coding the most amazing (a)synchronous parallel/serial code in your life. Enjoy!


# Comparison

Here are some comparisons between TaskGroup and other popular flow solutions.

* Promises
* Async.js

## Promises

Promises execute immediately, support result chaining, fail to catch/isolate uncaught async errors within the promise, and loses/silences errors that were not handled.

TaskGroup execution is controlled, supports concurrency configuration, supports optional result storage, catches/isolates uncaught async errors in environments with domains enabled, and throws unhandled errors to they are not lost/silenced if unhandled.

For example, let's read a directory with 10,000 files and get the stats:

```javascript
// Import
const {join} = require('path')
const {readdir, stat} = require('fs')

// Using promises
function readdirWithStatsPromise (path) {
  const result = {}
  return new Promise(function (resolve, reject) {
    readdir(path, function (err, files) {
      if ( err )  return reject(err)
      Promise.all(
        files.map((file) => new Promise(function (resolve, reject) {
          stat(join(path, file), function (err, stat) {
            if ( err )  return reject(err)
            result[file] = stat
            resolve()
          })
        }))
      ).then(() => result).catch(reject)
    })
  })
}
readdirWithStatsPromise(process.cwd()).then(console.log).catch(console.error)

// Using taskgroup
const {TaskGroup} = require('taskgroup')
function readdirWithStatsTaskGroup (path, next) {
  const result = {}
  const tasks = new TaskGroup(`fetch files with stats for ${path}`, {concurrency: 0}).done(function (err) {
    if ( err )  return next(err)
    next(null, result)
  })
  readdir(path, function (err, files) {
    files.forEach(function (file) {
      tasks.addTask(`fetch stat for ${file}`, function (complete) {
        stat(join(path, file), function (err, stat) {
          if ( err )  return complete(err)
          result[file] = stat
          complete()
        })
      })
    })
    tasks.run()
  })
}
readdirWithStatsTaskGroup(process.cwd(), function (err, result) {
  if ( err )  return console.error(err)
  console.log(result)
})

// Using taskgroup, with some cleaning
const {TaskGroup, Task} = require('taskgroup')
function readdirWithStatsTaskGroup (path, next) {
  const result = {}
  TaskGroup.create({
    concurrency: 0,
    name: `fetch files with stats for ${path}`,
    next: function (err) {
        if ( err )  return next(err)
        next(null, result)
    },
    tasks: files.map(function (file) {
      return Task.create(`fetch stat for ${file}`, function (complete) {
        stat(join(path, file), function (err, stat) {
          if ( err )  return complete(err)
          result[file] = stat
          complete()
        })
      })
    })
  }).run()
}
readdirWithStatsTaskGroup(process.cwd(), function (err, result) {
  if ( err )  return console.error(err)
  console.log(result)
})
```

It is worth noting the use of the optional names for the Task and TaskGroups which makes debugging a breeze as when errors occur the names are included in the traces. The testing library [Joe](https://github.com/bevry/joe) that is built on TaskGroup uses this ability to name suites (TaskGroups) and tests (Tasks) as well as to identify which tests and tasks have failed, succeeded, or remain incomplete. It is also worth noting that reading 10,000 files at once would have signficant immediate stress on the machine and may overwhelm the resources and error, crash or lock up less powerful machines. This can easily be catered for in TaskGroup by changing the concurrency from the parallel `0` value to a more reasonable value like `100`. To do such concurrency limiting by hand with Promises is incredibly difficult, heck, even doing serial execution (concurrency of 1) requires `Array.prototype.reduce` trickery:

```javascript
// Using promises serially
function readdirWithStatsPromise (path) {
  const result = {}
  return new Promise(function (resolve, reject) {
    readdir(path, function (err, files) {
      if ( err )  return reject(err)
      files.reduce((file) => new Promise(function (resolve, reject) {
        stat(join(path, file), function (err, stat) {
          if ( err )  return reject(err)
          result[file] = stat
          resolve()
        })
      }), Primise.resolve()).then(() => result).catch(reject)
    })
  })
}
readdirWithStatsPromise(process.cwd()).then(console.log).catch(console.error)

// Using promises serially, with some cleaning
function readdirWithStatsPromise (path) {
  const result = {}
  return new Promise(function (resolve, reject) {
    readdir(path, function (err, files) {
      if ( err )  return reject(err)
      resolve(files)
    })
  }).then((files) => {
    return files.reduce(function (file) {
      return new Promise(function (resolve, reject) {
        stat(join(path, file), function (err, stat) {
          if ( err )  return reject(err)
          result[file] = stat
          resolve()
        })
      })
    }, Promise.resolve())
  })
}
readdirWithStatsPromise(process.cwd()).then(console.log).catch(console.error)


// Using promises serially, with complete cleaning
function readdirPromise (path) {
  return new Promise(function (resolve, reject) {
    readdir(path, function (err, files) {
      if ( err )  return reject(err)
      resolve(files)
    })
  })
}
function statPromise (path) {
  return new Promise(function (resolve, reject) {
    stat(join(path, file), function (err, stat) {
      if ( err )  return reject(err)
      result[file] = stat
      resolve()
    })
  })
}
function statDirectoryPromise (path) {
  const result = {}
  return files.reduce(function (file) {
    return statPromise(join(path, file)).then(function (stat) {
      result[file] = stat
      return result
    })
  }, Promise.resolve(result))
}
function readdirWithStatsPromise (path) {
  return readdirPromise(path).then(statDirectoryPromise(path))
}
readdirWithStatsPromise(process.cwd()).then(console.log).catch(console.error)
```

That was some significant changes over several iterations of cleaning from our initial approach just to change the concurrency from parallel to serial. And even after all that cleaning, specifying an exact intermediate concurrency like 100, remains incomprehensible to do by hand without extra trickery. Plus, we still lose all the benefits outlined earlier that TaskGroup provides us, such as easier debugging, uniquely named tasks and groups of tasks, easy and specific concurrency, catching asynchronous errors, etc. If you want to get stuff done without needing trickery, TaskGroup is the best you'll find.

For a more detailed discussion about interop between the two, see [this discussion](https://github.com/bevry/taskgroup/issues/25). For a result chaining solution based on TaskGroup, see [Chainy.js](https://github.com/chainjs).

## [Async.js](https://github.com/caolan/async)

The biggest advantage and difference of TaskGroup over async.js is that TaskGroup has one uniform API to rule them all, whereas with async.js I found that I was always having to keep referring to the async manual to try and figure out which is the right call for my use case then somehow wrap my head around the async.js way of doing things (which more often than not I couldn't), whereas with TaskGroup I never have that problem as it is one consistent API for all the different use cases.

Let's take a look at what the most common async.js methods would look like in TaskGroup:

```javascript
// ====================================
// Series

// Async
async.series([
    function () {},
    function (callback) {
        callback()
    }
], next)

// TaskGroup via API, using config
TaskGroup.create({next, tasks: [
  function () {},
  function (callback) {
    callback()
  }
]}).run()

// TaskGroup via API, using chaining
TaskGroup.create().done(next).addTasks(
  function () {},
  function (callback) {
    callback()
  }
).run()

// TaskGroup via API
var tasks = TaskGroup.create().done(next)
tasks.addTask(function () {})
tasks.addTask(function (callback) {
    callback()
})
tasks.run()


// ====================================
// Parallel

// Async
async.parallel([
    function () {},
    function (callback) {
        callback()
    }
], next)

// TaskGroup via API, using config
TaskGroup.create({concurrency: 0, next, tasks: [
  function () {},
  function (callback) {
    callback()
  }
]}).run()

// TaskGroup via API, using chaining
TaskGroup.create({concurrency: 0}).done(next).addTasks(
  function () {},
  function (callback) {
    callback()
  }
).run()

// TaskGroup via API
var tasks = TaskGroup.create({concurrency: 0}).done(next)
tasks.addTask(function () {})
tasks.addTask(function (callback) {
    callback()
})
tasks.run()


// ====================================
// Map

// Async
async.map(['file1','file2','file3'], fs.stat, next)

// TaskGroup via API, using config
const tasks = ['file1', 'file2', 'file3'].map((file) => (complete) => fs.stat(file, complete))
TaskGroup.create({done, tasks}).run()

// TaskGroup via API, using chaining
TaskGroup.create().done(next).addTasks(
    ['file1', 'file2', 'file3'].map((file) => (complete) => fs.stat(file, complete))
).run()

// TaskGroup via API
var tasks = TaskGroup.create().done(next)
['file1', 'file2', 'file3'].forEach(function (file) {
    tasks.addTask(function (complete) {
        fs.stat(file, complete)
    })
})
tasks.run()
```

Another big advantage of TaskGroup over async.js is TaskGroup's ability to add tasks to the group once execution has already started - this is a common use case when creating an application that must perform its actions serially, so using TaskGroup you can create a serial TaskGroup for the application, run it right away, then add the actions to the group as tasks.

A final big advantage of TaskGroup over async.js is TaskGroup's ability to do nested groups, this allowed us to created the [Joe Testing Framework & Runner](https://github.com/bevry/joe) incredibly easily, and because of this functionality Joe will always know which test (task) is associated to which suite (task group), whereas test runners like mocha have to guess (they add the task to the last group, which may not always be the case! especially with dynamically created tests!).


# Showcase

There are hundreds of packages that make use of TaskGroup.

## [Complete Listing](https://www.npmjs.com/browse/depended/taskgroup)

{% embed url="<https://npmjs.org/browse/depended/taskgroup>" %}
The complete listing is available via the npm registry.
{% endembed %}

## [Kava](https://github.com/bevry/kava)

{% embed url="<https://github.com/bevry/kava>" %}

Mocha falls down when you have to create your tests dynamically, because Tests in Kava are Tasks, and Suites are TaskGroups, Kava will always know which tests are for which suite. Works tremendously well, with a modular architecture. Also works in the browser.

## [Event Emitter Grouped](https://github.com/bevry/event-emitter-grouped)

{% embed url="<https://github.com/bevry/event-emitter-grouped>" %}

Execute event listeners as TaskGroups, adding support for asynchronous listeners, parallel execution, and completion callbacks. Event Emitter Grouped is great for plugin infrastructures.

## [DocPad](http://docpad.org)

{% embed url="<https://docpad.org>" %}

A static site generator that uses TaskGroup and Event Emitter Grouped heavily to accomplish isolated asynchronicity — such that when a plugin fails, it doesn't bring down everything.


