How To Rock 🤟 Asynchronous Calls By Understanding JavaScript Callbacks, ES6 Promises And ES7 Async/Await 🔥😎
Tags: javascript, beginners
Published: 2019-07-15 18:09:16

*Cross-posted from [developer.blog](https://developer.blog/async-await)*

Callbacks can be incredible useful things when programming in JavaScript, however the coding can get messy when using them a lot. This post explains how promises and async/await from modern JavaScript specifications work and how they improve readability in your code.

In this post I'll use arrow functions, which you can read up on the first chapter of my [arrow function blog post](https://developer.blog/es6-arrow-functions).

## Callbacks
One of the most brilliant things in JavaScript is that functions are seen as objects. This makes it possible to pass functions as parameters to another function which can then call the passed function within. The passed function is called a callback function.

This comes in handy when processing tasks that sun asynchronous and we cannot be certain when exactly the task has finished, so we can process the resulted data. A real world example for this is requesting data from a REST API.

Here is an example with traditional callbacks of a function that -for demonstration purposes- needs 2 seconds to add two numbers:

```javascript
// Definition of the asynchronous function
const add = (a, b, callback) => {
    setTimeout(() => {
        const result = a + b
        callback(result)
    }, 2000);
}

// Calling the asynchronous function and passing the callback function
add(3, 6, sum => {
    // Execute this when result is ready
    console.log(`The sum is: ${sum}`)
})
```

When you execute that code, the add function is being called and after two seconds, the callback function will be executed with the result (logged to the console).

Doesn't look *that* bad, right? But there are two things that make this aproach tiresome to use:
- When trying to introduce error handling (something went wrong)
- When trying to use various callback functions after each other

### Error handling
Let's assume our ficticious function is only able to add positive numbers. We would want the user to know there was a problem when trying to process negative numbers.

```javascript
const add = (a, b, callback) => {
    setTimeout(() => {
        // Checking if the input numbers are right
        if(a >= 0 && b >= 0) {
            const result = a + b
            callback(result)
        } else {
            // Passing an error if there is a negative input
            callback(undefined, 'Numbers must be non-negative')
        }
    }, 2000);
}

add(3, -6, (sum, error) => {
    // If an error occured in the add function, display it
    if(error) {
        console.log(`An error occured: ${error}`)
    } else {
        console.log(`The sum is: ${sum}`)
    }
})
```

### Chaining
Executing various callbacks after each other (chaining), or otherwise known as "*callback hell*" can get really messy really fast.

Let's say we want to calculate the square of the resulting sum, and afterwards check if that square is an odd or even number. Each taking 1 fake additional second to execute.

```javascript
const add = (a, b, callback) => {
    setTimeout(() => {
        // Checking if the input numbers are right
        if(a >= 0 && b >= 0) {
            callback(a + b)
        } else {
            // Passing an error if there is a negative input
            callback(undefined, 'Numbers must be non-negative')
        }
    }, 2000);
}

const tripleDown = (a, callback) => {
    setTimeout(() => {
        callback(a * 3)
    }, 1000);
}

const isEven = (a, callback) => {
    setTimeout(() => {
        callback(a % 2 === 0)
    }, 1000);
}

add(3, -6, (sum, error) => {
    // If an error occured in the add function, display it
    if(error) {
        console.log(`An error occured: ${error}`)
    } else {
        square(sum, tripleResult => {
            isEven(square, isEvenResult => {
                console.log(`The sum is: ${sum}`)
                console.log(`The triple of the sum is: ${tripleResult}`)
                console.log(`The triple is even: ${isEvenResult}`)
            })
        })
    }
})
```

I think we can now agree that the code starts getting messy which makes it difficult to understand and maintain after a while.

## Promises
Promises to the rescue! In 2015, when ES6 was released, a nifty little feature was introduced which made it possible for developers to escape the callback hell.

A promise is exactly what the name suggests it is: It is a *promise* that there will be a result at some time in the future. That result can be successful, then the promise would be *fullfilled* or it could have failed, which would make the promise *rejected*. While there is no answer (yet), the promise is *pending*.

Let's write the code we had at the beginning (example of adding two numbers with a two second delay) with a promise.

```javascript
const add = (a, b) => {
    // Returning a promise that there will be an answer sometime
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            // Resolving the promise
            // This means it was successful
            resolve(a + b)
        }, 2000);
    })
}

// Executing the add function, *then* executing the callback.
add(2, 9).then(sum => {
    console.log(`The sum is: ${sum}`)
})
```

When the promise we created is being *resolved*, `.then()` is being executed and it will have whatever value has been passed in the resolve call.

### Error handling
Handling errors is a delight with promises. Instead of having the callback function to accept an extra parameter.

Instead of calling `resolve()` in the promise, we have to call `reject()` for the promise to end unsuccessfully. Let's extend the example with adding the resitrictions of not processing negative numbers:

```javascript
const add = (a, b) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if(a >= 0 && b >= b) {
                // The promise is being fullfilled successully
                resolve(a + b)
            } else {
                // The promise is being fullfilled unsuccessully
                reject('Numbers must be non-negative')
            }
        }, 2000);
    })
}
```

Handling that error is quite elegant now. We just add a `.catch()` to the promise execution:

```javascript
add(2, -9).then(sum => {
    // Processing the asynchonous function result
    console.log(`The sum is: ${sum}`)
}).catch(error => {
    // The error has being "caught"
    console.log(`An error occured: ${error}`)
})
```

### Chaining
To chain various asynchronous functions together is also a bit easier now. Here an example on chaining three times the same `add()` function. First adding 2+5, then the result of that + 43, then the result of that + 1000.

```javascript
add(2, 5).then(firstSum => {
    console.log('first sum', firstSum);
    return add(firstSum, 43)
}).then(secondSum => {
    console.log('second sum', secondSum);
    return add(secondSum, 1000)
}).then(thirdSum => {
    console.log('third sum', thirdSum);
}).catch(error => {
    console.log('error', error);
})
```

This is way cleaner and people were really excited about this back in 2015, because they could finally deliver cleaner code and kick their callback hells back where they came from (regular hell).

There were still two problems though:
- In the callback of each callback, you don't have access to the results inbetween (e.g. you can't access `firstSum` on the third `.then()`
- It still is not that intuitive to chain asynchronous functions together

These two problems were solved in ES7 which was released a year later.

## Async/Await
Async/Await is not a new technology, rather than a new toolset that's built on top of promises. It is designed to make asynchronous functions really easy to code and later on understand, with a syntax that flows off the keyboard pretty naturally. The great thing is, that something that's already programmed with promises will continue to work with async/await, because we just write code in a different manner rather than a new technology.

### async
When you put the `async` keyword in front of a function (doesn't matter if arrow or regular), it automatically returns a (resolved) promise rather than the value returned.

```javascript
const doAsynchronousStuff = async () => {
    return 4711;
}

// Returns: Promise { 4711 }
```

### await
When using the `await` in front of a function call, JavaScript *waits* for the promise to be fullfilled before continuing with the next line of execution.

**`await` can only be used inside a `async` function!**

Let's check out this example (assuming the `add` function from *Promises > Error handling* already exists:

```javascript
const doCalculations = async () => {
    const sum = await add(13, 99)
    return sum
}

doCalculations().then(result => {
    console.log(`The result is: {result}`)
})
```

### Error handling
The next line after an `await` function call is **only being executed when the promise has been fullfilled**. When it's being rejected, all future execution in the asynchonous function is being stopped.

There is a way though to catch errors for each individual `await`function call, using a good old-fashioned try/catch statement:

```javascript
const doCalculations = async () => {
    let sum;
    try {
        // Try to execute this...
        sum = await add(13, -99)
    } catch (error) {
        // If something goes wrong, we catch the error here
        console.log(`An error occured: ${error}`);
    }
    return sum
}
```

### Chaining
Chaining now is even easier than before. The way you write the code lets you even believe that they are synchronous calls, but in reality, all the `Promise` magic happens behind the scenes.

const doCalculations = async () => {
    const sum = await add(13, -99)
    const sum2 = await add(sum, 1000)
    const sum3 = await add(sum2, 9999)
    
    // You could access all three variables here.
    // For example to do comparisons

    return sum3
}

## Summary 🙌

**async/await** is an industry standard now and it is recommendable you use it, as it gives you many advantages. It is however important to know where it comes from and how it works under the hood. When using it, it's easy to forget we're actually doing asynchronous calls.

Now you should be all set to create your own libraries with Promise support and use existing libraries that do support promises already (all of the important ones do) in an easy and readable way.

*Photo by [Alex on Unsplash](https://unsplash.com/photos/OMF0olQno6M)*