Callbacks. What are they? Why do I have to use them? Will I ever understand them?
invoke
-
What is a callback function?
-
What is special about Javascript that allows functions to be used as callbacks?
-
Where might you see a callback function used?
I have a function in my code named getIngredients
. What is the difference between these lines of code? What does each represent?
getIngredients
This could also be written as const getIngredients = function()... it represents the function as a whole and what it can do.
getIngredients(shoppingList)
But when you write a function with parentheses, you invoke it - it will run. This function is meant to return an array of ingredients, so you can think of getIngredients(shoppingList)
as an array - the result of invoking the function.
function(shoppingList) {
getIngredients(shoppingList);
}
This is an anonymous function, it has not yet been invoked. "But it has parentheses!" I hear you shout. Yes... but the parentheses immediately following the word function are there to define the function and the argument it takes, just as in:
const aFunction = function(shoppingList) {
getIngredients(shoppingList)
}
getIngredients(shoppingList) also has parentheses and it will invoke the function, but not until the anonymous function gets called.
To pass a function to another function as a callback, we need to pass it in as a list of instructions so we can invoke it later. So with this in mind...
Which one of the above examples could you not pass into another function as a callback? Why?
Synchronous baking:
var ingredients = getIngredients(chocolateCakeRecipe);
var myCake = bakeCake(ingredients);
var decoratedCake = decorateCake(myCake);
var meal = eatCake(decoratedCake);
In this example, assuming each of these functions works synchronously, the Javascript engine would only be able to work on one task at a time.
The Javascript engine would be blocked while waiting for each function to complete. But getIngredients involves walking to the shop, so that might take some time... And bakeCake involves putting the cake in the oven and waiting for it to cook, which will definitely take some time!
We can think about the Javascript call stack as the head chef in a restaurant. Sure, he could do every single task himself, but he has a team of helpers around him who can let him focus on coordinating everything, while they do the time consuming work for him.
Let's rethink these functions as asynchronous functions, so that the head chef can be left alone to deal with organising the busy restaurant.
We want to use the following functions:
getIngredients
, bakeCake
, decorateCake
and eatCake
- Which function would we need to call first?
- What arguments would it need to take?
- What callback would we want to use when it finishes?
Let's pass in a function as a callback to be invoked by getIngredients, when it has finished, on the final result of the function.
Which of these would work? Why?
getIngredients(chocolateCakeRecipe, bakeCake(ingredients))
getIngredients(chocolateCakeRecipe, bakeCake)
Yes! The second example would pass in the list of instructions represented by bakeCake so that those instructions could be invoked as a callback at the correct time.
But that would only get us as far as baking the cake...
So we could use an anonymous function to allow us to give more detailed instructions for the callback and add another callback
getIngredients(chocolateCakeRecipe, (ingredients) => {
bakeCake(ingredients, decorateCake);
});
But we're still not there yet... we want to eat that delicious cake when it has been decorated.
So we can use --- another callback!
getIngredients(chocolateCakeRecipe, (ingredients) => {
bakeCake(ingredients, (cake) => {
decorateCake(cake, (decoratedCake) => {
eatCake(decoratedCake);
})
});
});
Now that our functions are asynchronous, they pass out some of their tasks to helpers. These helpers can get on with their tasks, and can wait around for time-consuming tasks to be completed, only coming back to bother the head chef when they are ready.
This is great! It has freed up our chef to get on with organising a busy restaurant. Now instead of taking 2 hours to bake a cake, he can get loads of baking done by delegating tasks to helpers.
BUT this can come back to bite us in the ass if we don't treat our async functions properly.
Let's say we wanted to assign the result of the getIngredients
function to a variable, like we did in the synchronous code above.
We could return the list of ingredients using the callback, right?
getIngredients(chocolateCakeRecipe, (ingredients) => {
return ingredients;
});
But if we tried to assign those ingredients to a variable, like this:
var ingredients =
getIngredients(chocolateCakeRecipe, (ingredients) => {
return ingredients;
});
what would happen?
The assignment of the variable would take place before the callback has been invoked. The variable ingredients
would be undefined
Once your functions use callbacks, you can't get the data back out. You have to keep using more and more callbacks to deal with the result of each function
Martin's Repl illustrates a lot of these concepts in live JS
https://repl.it/@martingaston/Testing-Callbacks
Let's try and get our heads around some of the nested callbacks we've seen in recent weeks:
Week 4: https://repl.it/@rudisimon/ColdGlassBits