Implementing javascript task runner
Tags: javascript, node, algorithms
Published: 2020-07-27 22:12:17

## Introduction.
Hi, I would be writing on how to(I would) implement a task runner in javascript. I assume you already have a bit of programming experience. While the language used in this article is javascript, the algorithm is pretty simple and can be easily replicated in any other programming language.
Also this is my first technical article and I am still learning the ropes of technical writing but I will try to explain the thought process as simple as I can. 
At the end of this article we should have a small javascript task runner library that can create a **task** and also **run** one or more task in any order.  I would also include the link to the full source code on GitHub at the end of this article.
 
I believe you would find this article interesting if you are interested in how task runners work internally, build systems or just interested in algorithms.

## What is a task runner?

A task runners is basically what the name implies, its a piece of code that **runs** specified **task(s)** based on a certain criteria. Examples of a task would include copying a file, pre-processing a file or it could be a simple computation. Apart from running a specified task, the task runner is also able to run sequence of task based on a specified order, so this way you can combine and run multiple tasks in different order as we would see shortly.

## Where can I use a task runner?

Task runners are usually used in build systems to automate repetitive processes. An example where this could work is when you want to customize bootstrap in your frontend project; typically you would have to specify tasks like `clear`; to clean up build directory, `build:sass`, `build:jquery` and `copy:css`; to copy compiled sass output to an output(build) directory etc. A task runner would be handy in this case and can be used to specify the tasks in the order you want them executed which also saves you development time.
Gulp and Grunt are examples of very popular javascript task runner and are used mostly for javascript build system.


## To Implement a Task runner

So far, from what we know above, our runner library would need two important methods;
1. One for defining the task.
2. Another for running our task(s) 

And in the rest of this article we would incrementally build upon this knowledge.

#### #Step 1: Define the  data structure

Define the data structure for saving our task. The first thing we need to do is to decide how best to save our data(tasks), and to do that we would use a dictionary(objects). The reason for using the dictionary data structure is because it is really fast to lookup and insert data. This would mean that we don't spend so much time in our program looking up and updating data, which would end up slowing down our library.      

```javascript
let runner = (function runner() {
    // tasks variable using the javascript object type
    var tasks = {}; 

})
```


#### #Step 2: Implement task function

The next step is to implement the task function. This is an important part of the task runner because this is how we are able to create task which we can run later. Our task function would require two parameters; the first parameter `taskName` defines the name of the task. This parameter is important because we would require it to run a particular task later. The second parameter `cb`; a function, defines the task and it could be passed arguments at runtime. 

```javascript

let runner = (function runner(){
  var tasks = {};
  
   function task(taskName, cb) {
    if (typeof cb != "function") return;
    if (typeof taskName != "string") return;
    
    if(!tasks[taskName]) {
      tasks[taskName] = function callback(args) {
        this.name = taskName;
        cb.apply(this, args);
      };
    }
  }
  
})
```
So in the above snippet for our `task` function, we check that the parameters are of the right types, `string` for the task name and `function` for the tasks. We also considered some edge cases such as task names should be unique, meaning tasks are only created if their task names don't already exist.

#### #Step 3: Implement the run function

The last thing we would consider for our library would be the ability to run the task we create, to do this, we would be implementing our `run` function.

We can consider some edge cases like the ability to pass arguments to the tasks at run time and also the ability to run one or more task with one or more optional `callback`. The callback(s) in the `run` function can be used to signify the end of all the tasks. The order of the callback(s) are relative to the task and are considered to be arbitrary.
 
```javascript

function run(...args){
    let callbacks, taskNames = null;
    callbacks = args.filter(arg => typeof arg == "function")
    taskNames = args.filter(arg => typeof arg == "string")


    if(taskNames){
      for (let taskName of taskNames) {
         // retreive passed arguments
        let args = taskName.split(":").splice(1);
        // retreive task name
        taskName = taskName.split(":")[0]; 
  
        let callback = tasks[taskName];
        if(callback) {
          setTimeout(callback.bind(this, args), 0);
        }
      }
    }

    if(callbacks) { 
      // callbacks for run
       for (const callback of callbacks) {
        setTimeout(callback, 0);
      }
    }
  }

  return {
    task: task,
    run: run
  }
```

The run functions accepts one or more `taskName` and optionally a `callback`. The tasks are executed in the order they are written and task arguments could be passed to the task using a colon in front of the `taskName`. So for a task name `copy`, you can pass an argument to the task like this `copy:foo` where `foo` is the argument passed at runtime.

Here are some example snippets on using our task runner library.

```javascript
const taskRunner = require("lauf");

taskRunner.task("copy", function(){ 
// time consuming copy task
console.log(this.name, "task is done!"); // copy task is done
})

taskRunner.task("build", function(){ 
// time consuming build task
console.log(this.name, "task is done!"); // build task is done!
})

taskRunner.task("sum", function(a, b){ 
let sum = a + b;
console.log("sum is", sum); // sum is 90
console.log(this.name, " task is done!"); // sum task is done!
})

taskRunner.run("sum:50:40", function() {
console.log("All long running task completed");
})

taskRunner.run("copy", "build", function() {
console.log("All long running task completed");
})
```

## Conclusion

In conclusion we discussed what a task runner is and where we can use them. We also learnt about the the basic methods of a task runner and how we can implement our own task runner. 

Also I hope you found this helpful. Don't forget to leave a comment, share or follow me on twitter. 

You can find a complete version of our task runner here.
{% github iamebuka/laufjs %}






[Banner photo by Sai Kiran Anagani](href="https://unsplash.com/@_imkiran")