Asynchronous JavaScript and JavaScript Promises

Hey - Nick here! This page is a free excerpt from my $99 course JavaScript Fundamentals.

If you want the full course, click here to sign up and create an account.

I have a 30-day satisfaction guarantee, so there's no risk (and a ton of upside!) in signing up for this course and leveling up your JavaScript developer skills today!

JavaScript is an asynchronous programming language. This means that it can only execute one statement at any given time.

Unfortunately, this makes it easy to introduce unintended bugs into your JavaScript code.

In this tutorial, you will learn how asynchronous JavaScript works and how to avoid its problems using a JavaScript feature called promises.

Table of Contents

You can skip to a specific section of this JavaScript tutorial using the table of contents below:

Everything in JavaScript is Asynchronous

Before we learn how to create promises in JavaScript, you should first understand what it means to say that everything in JavaScript is asynchronous.

To start, let's discuss how JavaScript is a single-threaded language. This means that only one statement can be run at a given time.

On the other hand, certain other programming languages are multi-threaded. This means they are capable of executing multiple statements concurrently.

The single-threaded nature of JavaScript can introduce some unintended bugs if you do not have a thorough understanding of how it works. This is especially true if you introduce any timeouts into your code. Let's visualize this with some JavaScript code!

First, let's log two strings to the console:

console.log('The start of our program');

console.log('The end of our program');

By this point in the course, you should understand that the following output will now appear in the console:

The start of our program

The end of our program

Between these statements, let's add an instance of the setTimeout function, which accepts two arguments: a callback function, and an amount of time to wait before executing the callback function. We'll keep things simple and add a callback function that logs The middle of our program to the console after waiting for 5 seconds (or 5000 milliseconds), like this:

console.log('The start of our program');

setTimeout(() => console.log('The middle of our program'), 5000);

console.log('The end of our program');

What do you think happens when you run this code? After waiting for five seconds, here is what appears in our console:

The start of our program

The end of our program

The middle of our program

What gives?

Well, the asynchronous nature of JavaScript means that the script does not stop running after it hits the setTimeout function. It moves on to the next console.log statement and places its own callback function into something called the Callback Queue.

Functions that are waiting in the Callback Queue are run whenever your JavaScript application is not busy running any other code.

To fix this problem, you could refactor your code so that the console.log statements for both The middle of our program and The end of our program are within the setTimeout function.

Here is what I mean:

console.log('The start of our program');

setTimeout(function () {

    console.log('The middle of our program');

    console.log('The end of our program');

}

, 5000);

Overall, the main takeaway that you should understand from this section is that JavaScript is single-threaded, which means that it can only run one function at a time. This can introduce unintended functionality when paired with setTimeout functions and other built-in timer functionality.

The solution to this is JavaScript promises, which we explore in the next section of this tutorial.

JavaScript Promises

JavaScript promises allow you to execute code after waiting for something else to happen. Promises are created immediately, but do not resolve until they are ready. Whether or not a promise is ready to resolve is dependent on some outside factor - whether data has been imported, for example.

To illustrate how promises work in JavaScript, let's create a function called makePie. This function will contain a promise called piePromise and will return this promise using the return keyword.

function makePie(){

    const piePromise = new Promise();

    return piePromise;

}

This is not just a silly example - it parallels the real-world situation of ordering a pie from a bakery. When you call in to a bakery and order a pie, the pie is not given to you immediately. Instead, you rely on the promise of the bakery that the pie will be delivered when it's ready - once the ingredients have been put together and the pie has actually been baked.

Let's return to the makePie function. The promise that it contains isn't actually done yet. JavaScirpt promises accept functions as their arguments. Those functions require two parameters:

  • The resolve function - the function that will be executed when the promise is resolved.
  • The reject function - the function that will be executed if an error occurs with the promise.

Let's add this functionality to the piePromise promise. To keep it simple, we will pass in Your pie is ready! to the resolve function and Something went wrong with your pie! to the reject function:

function makePie(){

    const piePromise = new Promise(function(resolve, reject){

        resolve('Your pie is ready!');

        reject('Something went wrong with your pie!')

    });

    return piePromise;

}

Let's modify this makePie function further so that it accepts a pieType argument - think Apple, Pumpkin, or your personal favorite type of pie. After that, we can interpolate in this pieType parameter to the resolve and reject functions.

function makePie(pieType){

    const piePromise = new Promise(function(resolve, reject){

        resolve(`Your ${pieType} pie is ready!`);

        reject(`Something went wrong with your ${pieType} pie!`)

    });

    return piePromise;

}

Now what happens when we run this function?

Well, if you run makePie('Apple'), here is the output:

Promise {<resolved>: "Your Apple pie is ready!"}

This shows that the promise was resolved.

Adding Delays to JavaScript Promises

So far, we have built a function called makePie that contains a piePromise promise. However, our promise is missing an important piece of functionality - the ability to wait before executing.

To add this functionality, we need to add a setTimeout function within piePromise. Here's how you would do this with a 2 second (or 2000 millisecond) delay:

function makePie(pieType){

    const piePromise = new Promise(function(resolve, reject){

        

        setTimeout(function(){

        resolve(`Your ${pieType} pie is ready!`);

        reject(`Something went wrong with your ${pieType} pie!`)},

        2000

        );

    });

    return piePromise;

}

Now if you run makePie('apple') and immediately expand the object that it returns, you will see the following:

[[PromiseStatus]]: "pending"

[[PromiseValue]]: undefined

However, if you wait two seconds (or more) before doing this, you will instead see:

[[PromiseStatus]]: "resolved"

[[PromiseValue]]: "Your apple pie is ready!"

This shows that we have successfully added a delay to our JavaScript promise! In the next section, we will see how

How to Access the resolve and reject Values in a JavaScript Promise Using the then Keyword

In the last section of this tutorial, we were working with the following function:

function makePie(pieType){

    const piePromise = new Promise(function(resolve, reject){

        

        setTimeout(function(){

        resolve(`Your ${pieType} pie is ready!`);

        reject(`Something went wrong with your ${pieType} pie!`)},

        2000

        );

    });

    return piePromise;

}

Although we have added the resolve and reject values to this promise, we have not yet seen how to access these values.

Fortunately, it is very easy. Once the promise has been resolved, you can access the values by using the then method. The then method accepts a function and passes in the promise as the argument to that promise. If you log the promise to the console within the then method, you will access either the resolve or reject statement.

Here is an example:

makePie('apple').then(pie => console.log(pie))

//Logs 'Your apple pie is ready!' to the console once the promise is resolved

We will learn more about the then method later in this tutorial.

Chaining Together Promises

Earlier in this tutorial, we learned how the setTimeout function combined with JavaScript's asynchronous nature can lead to bugs in your JavaScript code.

Using the setTimeout to create delays in JavaScript becomes especially problematic when you want to chain together multiple delays. To do this, you need to nest multiple setTimeout functions inside of each other, like this:

(function(){

    setTimeout(function(){

        console.log("Hey.");

        setTimeout(function(){

            console.log("Hey again.");

        },2000);

    },3000);

})();

This quickly becomes unreadable if you scale this up further.

Fortunately, promises provide a better way for chaining together multiple delays in JavaScript. After creating a promise in JavaScript, you can use the then method to execute another function after the promise is resolved.

As an example, consider the following code:

This code creates the makePie function, then creates a promise and saves it to the variable thePiePromise. The code then invokes the then method on thePiePromise and logs End to the console.

function makePie(pieType){

    const piePromise = new Promise(function(resolve, reject){

        

        setTimeout(function () {

        resolve(`Your ${pieType} pie is ready!`);

        reject(`Something went wrong with your ${pieType} pie!`);

    },2000

        );

    });

    return piePromise;

}

const thePiePromise = makePie('apple')

console.log('Start')

thePiePromise.then(() => console.log('Pie is done'))

console.log('End')

Here's what will be generated in the console when you run this code:

Start

End

Pie is done

Of course, the last line is only generated after 2 seconds (when the promise is resolved).

Now let's refactor this code so that End is logged to the console 2 seconds after Pie is done is.

The first thing we need to do is modify the function within our then method so that it is no longer an arrow function.

const thePiePromise = makePie('apple')

console.log('Start')

thePiePromise.then(function (){

    console.log('Pie is done')

})

console.log('End')

Once this is done, we need to return another invocation of the makePie function.

const thePiePromise = makePie('apple')

console.log('Start')

thePiePromise.then(function (){

    console.log('Pie is done')

    return makePie()

})

console.log('End')

This will trigger the setTimeout function for another 2000 milliseconds.

Now we can chain another invocation of the then method, and within that then method, we can add an easy arrow function that logs End to the console.

Here's the code:

const thePiePromise = makePie('apple')

console.log('Start')

thePiePromise.then(function (){

    console.log('Pie is done')

    return makePie()

}).then(() => console.log('End'))

Boom! We're done! Once you run this code, you will see the following output;

Start

Pie is done

End

Because of the setTimeout function contained within the makePie function, the second line appears after 2 seconds and the last line appears after 4 seconds.

Final Thoughts

In this tutorial, you learned about the asynchronous nature of JavaScript code. You also learned how to write and manipulate JavaScript promises.

Here is a brief summary of what we discussed in this lesson:

  • What it means to say that "everything in JavaScript is asynchronous"
  • How JavaScript's asynchronous nature can introduce bugs, especially when using the setTimeout function
  • How to write JavaScript promises
  • How to use promises to trigger delayed functionality using the then method
  • How to chain together promises using multiple then methods