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:
var Task =require('taskgroup').Task// Create a task for our synchronous functionvar task =newTask(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 completedtask.done(function (err, result) {// Do something now that the task has completed ...console.log([err, result])/* [null, "a sychronous result"] */})// Execute the tasktask.run()
And an asynchronous task like so:
var Task =require('taskgroup').Task// Create a task for our synchronous functionvar task =newTask(function (complete) {// Do something asynchronoussetTimeout(function () {// the error is the first callback argument, and the results the following argumentsreturncomplete(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 completedtask.done(function (err, result) {// Do something now that the task has completed ...console.log([err, result])/* [null, "an asychronous result"] */})// Execute the tasktask.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!
var TaskGroup =require('taskgroup').TaskGroup// Create our serial task groupvar tasks =newTaskGroup({storeResult:true})// Add an asynchronous task to ittasks.addTask(function (complete) {setTimeout(function () {returncomplete(null,"a result") },5000) // execute the timeout after 5 seconds})// Add a synchronous task to ittasks.addTask(function () {return"a synchronous result"})// Add our completion callback for once the tasks have completedtasks.done(function (err, results) {console.log([err, results])/* [null, [ [null, "an asychronous result"], [null, "a sychronous result"] ]] */})// Execute the task grouptasks.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.
var TaskGroup =require('taskgroup').TaskGroup// Create our parallel task groupvar tasks =newTaskGroup({storeResult:true, concurrency:0})// Add an asynchronous task to ittasks.addTask(function (complete) {setTimeout(function () {returncomplete(null,"a result") },5000) // execute the timeout after 5 seconds})// Add a synchronous task to ittasks.addTask(function () {return"a synchronous result"})// Add our completion callback for once the tasks have completedtasks.done(function (err, results) {console.log([err, results])/* [null, [ [null, "a sychronous result"], [null, "an asychronous result"] ]] */})// Execute the task grouptasks.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:
var TaskGroup =require('taskgroup').TaskGroup// Create our serial task groupvar tasks =newTaskGroup({storeResult:true})// Add the first serial tasktasks.addTask(function () {return"first serial task"})// Add a nested group of tasks that you would like executed in paralleltasks.addGroup(function (addGroup, addTask) {// Set this nested group to execute in parallelthis.setConfig({concurrency:0})// Add an asynchronous task to the nested groupaddTask(function (complete) {setTimeout(function () {returncomplete(null,"a result") },5000) // execute the timeout after 5 seconds })// Add a synchronous task to the nested groupaddTask(function () {return"a synchronous result" })})// Add the second serial tasktasks.addTask(function () {return"second serial task"})// Add our completion callback for once the tasks have completedtasks.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 grouptasks.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.
var TaskGroup =require('taskgroup').TaskGroup// Create our serial task groupvar tasks =newTaskGroup({storeResult:true})// Add an asynchronous task to the TaskGrouptasks.addTask(function (complete) {setTimeout(function () {returncomplete(newError("the first task failed")) },5000) // execute the timeout after 5 second})// Add a synchronous task to the TaskGrouptasks.addTask(function () {return"the second task"})// Add our completion callback for once the tasks have completedtasks.done(function (err, results) {console.log([err, results])/* [Error("the first task failed"), [ [Error("the first task failed")] ]] */})// Execute the task grouptasks.run()
Which comes in very handy when dealing with asynchronous parallel code:
var TaskGroup =require('taskgroup').TaskGroup// Create our parallel task groupvar tasks =newTaskGroup({soreResult:true, concurrency:0})// Add an asynchronous task to the TaskGrouptasks.addTask(function (complete) {setTimeout(function () {returncomplete("the first task failed") },5000) // execute the timeout after 5 seconds})// Add an asynchronous task to the TaskGrouptasks.addTask(function (complete) {setTimeout(function () {returncomplete("the second task failed") },1000) // execute the timeout after 1 seconds})// Add our completion callback for once the tasks have completedtasks.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 grouptasks.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:
// Execute the tasktask.run()// Add our completion callback for once the task has completedtask.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 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, 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!