Progress pill
Advanced JavaScript

Concurrency with callbacks

JavaScript and NodeJS Fundamentals

Concurrency with callbacks

  • SetTimeout
  • Callbacks
Until now, our code has been synchronous: it runs one line at a time, in order. But some things in the real world take time, and we don’t want the entire program to pause while waiting.
In this chapter we’re going to introduce a new concept: concurrency. It allows us to manipulate the order in which things gets done. This is useful when dealing with things like timers, user input, or reading files from disk. JavaScript offers different tools for doing concurrency.

setTimeout

The function setTimeout lets you run a function later, after some time has passed.
Example:
console.log("Start") setTimeout( () => console.log("This runs after 2 seconds"), 2000 ) console.log("End")
This prints:
Start End This runs after 2 seconds
Even though setTimeout appears in the middle of the code, it doesn't block the rest. Instead, it schedules the function to run later, and immediately moves on.
The 2000 means 2000 milliseconds (which is 2 seconds). Here's a more verbose and beginner-friendly rewrite of the Callbacks and Promise sections, using data manipulation and clear annotations throughout:

Callbacks

A callback is just a function that we give to another function so it can be called later.
Let’s look at a real example using numbers. Imagine we have a list of numbers, and we want to double each one of them, and then apply a function (the callback) to the resulting "doubled" array, but we want to do it after a small delay, as if we were waiting for something slow (like loading data from the internet).
Here’s a function that does that using a callback:
function doubleNumbers(numbersArray, callback) { // Pretend we're doing a slow operation using setTimeout setTimeout(() => { // Use the map method to create a new array where each number is doubled const doubled = numbersArray.map(n => n * 2) // When we're done, we call the callback function with the result callback(doubled) }, 1000) // Wait 1 second before running the code inside }
Let’s try to use this function:
const input = [1, 2, 3] doubleNumbers(input, function(result) { console.log("Here is the doubled array:", result) })
After 1 second, this prints:
Here is the doubled array: [ 2, 4, 6 ]
What’s happening here?
  1. We pass input as the list of numbers we want to double.
  2. We also pass a callback function that tells the program what to do after doubling.
  3. Inside doubleNumbers, we simulate a delay using setTimeout, then we do the doubling.
  4. Once that’s done, we call the callback on the resulting "doubled" array.
This technique works, but imagine you want to do more steps after that, like filter out small numbers and then add them up. You’d have to nest more callbacks like this:
doubleNumbers(input, function(doubled) { filterBigNumbers(doubled, function(filtered) { sumNumbers(filtered, function(total) { console.log("Final result:", total) }) }) })
This is hard to read and messy. This style is called callback hell, and it’s exactly what Promise was created to fix.