In this lesson we manage to remove all of the nested loops that helped us towards a partial solution in the first lesson. We create a function
getTasks, that can, only under certain conditions, call itself. This is the basis of recursion and often leads to cleaner, shorter code that can handle more dynamic input.
Recursion is a great way to avoid nested loops like this. This example from the previous lesson shows how we can create a flat array given some input such as Build and a configuration object like this. Because Build exists as a key on the config object, we would then add each of these items to a tasks array. Unless the item we're adding is also a key here, in which case we will instead add those items that are associated to it, the idea being that we will end up with this flat array that only contains task names.
Let's recreate the same result, but instead of using nested loops like this, we'll use a recursive function. We'll call it Get Tasks, which will take our input and we can loop over that as we did before.
On the first run-through, Task is now going to be equal to Build, in which case we can check if it exists on the configuration object. If it does we now need to look at this array. But whereas previously we would have another nested loop inside here. If you look at Get Tasks it actually just takes in an array. This is the recursive part of this function.
We can actually call the same function again, but this time passing in a different array. When this runs, and this is equal to Build, we see it here. We get access to the array here and we pass it back in to get tasks.
Now an input runs. The first item is going to be JS. This check will occur again. It will see it here and then it will call Get Tasks again. This time, it's being called with this array. For the first iteration Tasks will now be equal to lint. Because lint does not exist as a key on the config object, this block of code never runs.
We can say else tasks.push task. This is an important part of a recursive algorithm, is that it must end somewhere. In our case we are going to keep calling this Get Tasks function with a different array each time until the items in that array no longer match a key on the left here, at which point we simply add it to the array and then the whole thing repeats.
This means we end up adding lint and uglify from the JS command. Then it runs again with CSS and the whole process repeats. Task ends up being equal to Sass and then Cssmin, which don't match here and get added to the array. Finally, when both of those paths have been exhausted this is called with VersionRev. VersionRev does not exist as a key here, so it also ends up at this point.
Now if we call this function to begin the process, we can pass along our input, run it, and you can see we get exactly the result we wanted with just this simple little function. Not only is this less code and easier to maintain, it can also do something that the previous example couldn't. If we do the same thing, add another command code Dist that calls Build and then Deploy, change our input to instead be Dist, then if we run it you can see we get the correct result.
This is one of the main benefits of using recursion. It doesn't matter how deep the nesting goes. The function can just keep calling itself over and over again with different arrays until eventually, this conditional evaluates to false. Then we end up inside of this Else block instead, in which case we just add the current item to the Tasks array and that part of the recursive loop is over.