JavaScript - Demystifying Callbacks, Promises and Async Functions
Tags: javascript, beginners, development, software
Published: 2021-06-04 17:08:11

Imagine these two scenarios:

1) It is a rainy Monday and I'm alone at home, working as hell. It is around noon and my stomach starts to ache: "It's time do eat, idiot!". I grab my mobile phone and open the most famous food delivery app and ask for a pizza (be healthier, it's not even weekend, you b*tch!). I select the ingredients, choose to pay directly to the deliveryman and click "Ask for bloody pizza". I sit on the couch, do nothing, and wait for my pizza to be delivered.

2) It is a rainy Monday and bla bla bla the same as above but, while the pizza isn't delivered I decide to clean my desk, do the dishes from last night (lazy bastard!), watch some TV and practice acoustic guitar.

It is quite easy to identify the difference between the two scenarios.
In the first one, my life stops completely while I wait for the pizza and, in the second one, I do plenty of other things while it is not delivered.

These two examples demonstrate (or at least try to) what _synchronism_ is. This is what I'm going to talk about in this article: Synchronous and Asynchronous JavaScript operations implemented using: _callbacks_, _promises_ and _async functions_. Ta dãã! 

---

### Callbacks

_Callback_ is a function that is passed as argument to another function (a [high-order function](https://dev.to/damcosset/higher-order-functions-in-javascript-4j8b)) and it is executed when something specific occurs. A very common usage is when fetching data using [Ajax](https://www.w3schools.com/xml/ajax_intro.asp), for example.
If you have  ever used [`setTimeout`](https://www.w3schools.com/jsref/met_win_settimeout.asp) or [`setInterval`](https://www.w3schools.com/jsref/met_win_setinterval.asp) you have already used _callbacks_.

In the following example the `callback` function is passed as an argument to `setTimeout` and it is executed after 5 seconds.

```javascript
function callback() {
  console.log("Time's up!")
}

setTimeout(callback, 5000)
```

Callbacks are useful to deal with asynchronous cycles. Check the following example. Each time a function receives another function as an argument, it executes its own action and then executes the callback, which does the same thing and so on.

```javascript
function orderPizza(function() {
  console.log('Pizza ordered!')

  waitForPizzaToBeDelivered(function() {
    console.log('Pizza delivered!')

      payForPizza(function() {
        console.log('Pizza paid!')
 
        eatPizza(function() {
           console.log('Pizza finished! :(')
        })
      })
   })
})
```

The problem is what we call _[callback hell](http://callbackhell.com/)_; because, the more complex the scenario, the more scope levels and callbacks will be necessary, making it harder to read and even maintain it, creating this "pyramid" effect.

### Promises

Since the very beginning, Promises have been trying to represent asynchronous routines sequentially and implement a better error treatment. Let´s see how the example above is written using Promises.

```javascript
orderPizza()
.then(function(pizza) {
  console.log(`A ${pizza.flavour} has been ordered!`)  
  return waitForPizzaToBeDelivered()
})
.then(function() {
  console.log('Pizza delivered!')
  return payForPizza()
})
.then(function() {
  console.log('Pizza paid!')
  return eatPizza()
})
.then(function() {
  console.log('Pizza finished :(')
})
```

The main difference between using _callbacks_ and _promises_ is that, using _promises_ we can avoid the "pyramid" effect created by callbacks within callbacks, making the code easier to understand.

At the end of each `.then()` it is possible to return:

- **a value like an object, an array, a string, etc.**
In this case the next `then` in sequence will be immediately executed.

- **another promise**
This is what we've done in the example above.
For each `then` in the sequence to wait for an operation to be completed, a promise must be returned. Once the promise has been resolved, the execution proceeds.

So, to make it simpler. **What exactly is a promise?**

A Promise is used to represent a value and make sure that you will receive that value.
When the function finishes the required processing to return what it has _promised_ to you, you will be warned.
Sometimes, things can go wrong (the connection is down, for example) and the value will never be delivered.

This is how a Promise is created.
We will use the simple `setTimeout` example.

```javascript
function sendDelayedMessage(message, milliseconds) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if(message) {
        resolve(message)
      } else {
        reject('Message is empty!')
      }      
    }, milliseconds)
  })
}
```

In the example above we can see that the function `sendDelayedMessage` creates and returns a new Promise. A Promise constructor gets a function named _executor_ as argument which, then, gets two other functions as arguments: `resolve` and `reject`. They are "magically" injected there and you just need to call them at the right time according to your needs.

- `resolve`: it must be called to inform that the promise has been deferred or "resolved". If the routine has any result, it must be passed as argument to it.

- `reject`: it must be called in case there were any errors. By executing it you are informing that the promise has failed or has been "rejected".

It is possible to treat errors in `then()` chains using `.catch()`:

```javascript
sendMessage('Hi, beautiful people!', 5000)
.then(message => {
  console.log('Message successfully sent', message)
})
.catch(error => {
  console.error('Something wrong happened!', error)
})
```

### Async Functions

If Promises were created to increase readability and maintainability, JavaScript has given a huge step forward in both aspects with _async functions_.

_Async Functions_ make **asynchronous** code look like **synchronous**.

Here is our bloody pizza example written using Async Functions:

```javascript
async function pizzaFlow() {
  const pizza = await orderPizza();
  console.log(`A ${pizza.flavor} pizza has been ordered!`);

  await waitForPizzaToBeDelivered();
  console.log('Pizza delivered!');

  await payForPizza();
  console.log('Pizza paid!');

  await eatPizza();
  console.log('Pizza finished :(');
}
```

**It is only possible to use "await" within functions marked as "async"**

When JavaScript finds an `await` instruction, it will wait for that operation to complete in order to continue with the execution flow.
Async function may be briefly explained as "[syntactic sugar](https://www.quora.com/What-is-syntactic-sugar-in-programming-languages)" for Promises.

---

### Conclusion

There are several ways to deal with asynchronous operations in JavaScript and none of them is considered "wrong"! They all have their pros and cons. The most important is to understand how they work and when to use them properly according to the situation.

**This post was heavily based on the article [Asynchronous JavaScript: callbacks, promises and async functions](https://medium.com/@alcidesqueiroz/javascript-ass%C3%ADncrono-callbacks-promises-e-async-functions-9191b8272298)*** by [Alcides Queiroz](https://medium.com/@alcidesqueiroz)

_*Written in Portuguese_

I hope you liked it.
Please, comment and share!

_Cover image by [@ryanmfranco](https://unsplash.com/@ryanmfranco)_