Skip to content

Instantly share code, notes, and snippets.

@arrested-developer
Created September 4, 2018 09:22
Show Gist options
  • Save arrested-developer/6fd1dfb73e1face10cd4783499f156f4 to your computer and use it in GitHub Desktop.
Save arrested-developer/6fd1dfb73e1face10cd4783499f156f4 to your computer and use it in GitHub Desktop.
The Great Callback Bake Off
<style> .centered { width:100%; margin-bottom: 1em; } </style>

The Great Callback Bake Off

Callbacks. What are they? Why do I have to use them? Will I ever understand them?


Technical words

invoke ▶️ anonymous 🕵🏼‍ reassign 🔴 = 🔵 call stack 👩🏻‍🍳 blocking ✋ synchronous ⚡️ asynchronous 🐢

Let's check our knowledge:

  • 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?

Invoking a function

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?

Blocking and non-blocking code

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!

Freeing up the call stack

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);
        })
    });
});

Using asynchronous functions

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

Further understanding

Martin's Repl illustrates a lot of these concepts in live JS

https://repl.it/@martingaston/Testing-Callbacks

Applying it

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

Week 5: https://repl.it/@rudisimon/MajorKlutzyExtension

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment