Hero Image

Introduction to Asynchronous Programming

Table of Contents

Learning Objectives

Videos

By the end of this course you should be able to:

  • Understand asynchronous programs and their logic flows
  • Write asynchronous programs with modern, idiomatic JavaScript Syntax
  • Be aware of potential issues with asynchronous flows
  • Give good answers to asynchronous interview questions

course overview

Synchronous Concepts and Asynchronous Callbacks

First off, we’ll spend some time with the basics. We’ll start talking about synchronous programs and some of the most common terms that often get mixed up. At the end of the lesson, we’ll do a recap of callbacks that will leave you in a great place to get started with Promises.

Sequencing Events - Promises

Lesson 2 will be an introduction to Promises and how they solved some of the issues with callbacks in asynchronous programs. We'll cover the general syntax with lots of hands-on practice. Promises are where we’ll spend the bulk of this course since they are the most common form of handling asynchronicity in modern JavaScript. We will also cover many practical applications of Promises. First with Fetch, the modern promise-based syntax for XHTTP requests, and then with promise chaining methods to handle more advanced asynchronous actions. Once you have these under your belt, you’ll have a firm understanding of Promises and be ready for the next layer on top of that syntax - Async/Await.

Sequencing Events - Async/Await

The final lesson of this course will be an introduction to the Async/Await syntax introduced in ES6. In reality, this is just some convenient syntax packed around Promises that can help us reason about asynchronous functions in a synchronous way.

Resources

Here are some resources that will put you in a good place for starting this course:

Introduction to Asynchronous Programming

Key Points

The JavaScript programs we’ve written so far run one function at a time and each function runs to completion before calling the next function in the chain. Programming in this synchronous way is fairly easy to reason about—write the logic for one task at a time and then run the tasks in order.

Things get more complicated though when tasks don't just run sequentially. Either for efficiency reasons or to handle work done on another machine, we have to learn to manage when each step of our programs happen in a much more intentional way. This is the crux of asynchronous programming—it's all about “whenever [x] finishes, do [y]” or “while [x] is waiting, proceed with [y]”—and this scenario happens all the time in real life. Some of the places asynchronous code is necessary are:

APIs (Asynchronous Programming Interfaces) Any external processes Long-running processes (reading files, uploading files, etc…) Writing more efficient programs that better utilize CPU The ability to write asynchronous programs is becoming commonplace in all modern languages, and the concepts you learn in this course will translate well to any language you end up using.

synch vs asynch

Programming terms

Synchronous Programming

What is Synchronous Programming?

Be patient with me a minute as we go back to the very basics. All this talk of asynchronous code… but what exactly does it mean to be “synchronous”?

An Analogy

Understanding the synchronous process is easiest when you see it in comparison with an asynchronous process. There are a lot of really good analogies for synchronous/asynchronous programming, you might have heard some of them already. Listen to the video above to hear the coffee shop analogy.

Analogy Takeaways

So what is synchronous programming? It is programming in a way that doesn't try to be efficient with time. Like the barista who waits for the coffee machine as it makes coffee instead of moving on to other tasks, synchronous code will not move on from a task even if it is just waiting for a response from something else.

You can see how the asynchronous approach is more efficient, but also can be more complicated. Another important thing to notice in this analogy is that there is only ever one barista, we haven't sped up the process by adding another worker, instead the barista is effectively using their time by continuing with work they can do, while waiting for another task to complete.

Threads

Threads are where computers do work, they can do one thing at a time. Modern computers achieve work quickly by spreading tasks across multiple threads.

But JavaScript is single-threaded, we only get one thread on which to do work (with exceptions...but we'll get to that later). Only one thing can happen at a time, so we have to learn to use our single thread as efficiently as possible.

Synchronous programs don't try to use the thread efficiently, they just program tasks to be done and if one task takes a long time - even if it is just waiting for something to happen - the thread is blocked and cannot move onto the next task until the current one is completely finished.

The inverse of this shows us that Asynchronous programs use clever methods and syntax to their advantage with a goal of getting as much usefulness out of the single thread as possible.

threads single vs. multi

Blocking vs Non-Blocking

Blocking and Non-Blocking Recap

Blocking refers to a task that stops all work on the thread until it is complete. A non-blocking task allows the program to go on with other tasks while waiting for something to finish. For instance, if my application makes a blocking request to an API, the entire thread is stuck while it is just waiting for that information to come back. If we change it to use a non-blocking request our program can continue on to complete other tasks and deal with the API response when it becomes available.

Synchronous programs use blocking code. Asynchronous programs use non-blocking code when it is beneficial. By learning to write non-blocking code to create asynchronous programs, we can reduce inefficiencies and make better use of the computing power available to us.

This was the theory - now on to the code! In the next section we will do some code walkthroughs.

blocking vs non-blocking

Walkthrough Recap

Now we've seen some quick examples that hopefully illustrated the differences between blocking and non-blocking code.

The important thing is that we saw some asynchronous syntax in JavaScript. We learned that JavaScript moves I/O operations to a new thread via an internal API, which is why using the timer in our example was automatically non-blocking. This is what is meant when JavaScript is described as a "single threaded programming language with asynchronous non-blocking I/O".

non-blocking vs  blocking

Resources

If you want a list of the JavaScript internal API's, I found it hard to find a good list, but this one by Mozilla is pretty good: Introduction to web APIs.

For another explanation of the asynchronous nature of JavaScript, here is a good article that proceeds to many of the topics we will cover in this course: Getting to know asynchronous JavaScript: Callbacks, Promises and Async/Await.

Many of the articles about asynchronous JavaScript focus on JavaScript running in the browser, here are the Node docs for a look from the server side perspective: Overview of Blocking vs Non-Blocking.

Key Terms - Asynchronous vs. Concurrent vs. Parallel

keyTerms

Asynchronous (or Non-Blocking)

Refers to the management of a single thread that can move past tasks that are waiting to complete the rest of the program. Synonymous with non-blocking.

asynch

Parallel

Running two processes on two separate threads simultaneously

parallel

Concurrent

Achieving the appearance of simultaneous computation by switching between tasks on an active thread

concurrent

Quiz

quiz1

quiz2

Basic takeaway:

JavaScript is a single-threaded language with non-blocking I/O.

Introduction to Sequencing Events - Callbacks

Sequencing Events with Callbacks

You have used callbacks already: any function passed to another function as argument is a callback. We aren’t going to go back to the basics with callbacks in this section, but we are going to cover how callbacks can create asynchronous flows.

Callbacks For Asynchronous Functionality Recap

Callbacks are the vehicle for our asynchronous tasks to rejoin the main thread. When work moves off the thread, we send it with a function to run on the main thread when it is complete. This allows us to continue acting on the result of an asynchronous event whenever the event completes. In this way, passing functions as callbacks allows us to schedule reactions to asynchronous events without having to know how long the asynchronous event will take.

More Resources

If you need a refresher on JavaScript callbacks, here are some resources

If you want to go further into understanding how JavaScript handles these things at a lower level, I highly recommend this talk by Jake Archibald, the concepts you learned in this lesson will help you understand the concepts he covers:

Jake Archibald: In The Loop - JSConf.Asia

Chaining for Easier Reading Recap

Here is the code from the video above:

// nested-callback approach

const mockAPI = (returnValue) => (arg, cb) => {
    setTimeout(() => cb(returnValue), 2000)
}

const fetchSession = mockAPI({ id: "123765" })
const fetchUser = mockAPI({ firstname: "Bob" })
const fetchUserFavorites = mockAPI([ "lions", "tigers", "bears" ])

const runCallbacks = () => {
    fetchSession("session-id", (session) => {
        fetchUser(session, (user) => {
            fetchUserFavorites(user, (favorites) => {
                console.log(favorites)
            })
        })
    })
}

// flat-callback approach

const runCallbacksFlat = () => {
    const handleFavorites = (favorites) => {
        console.log(favorites)
    }

    const handleUser = (user) => {
        fetchUserFavorites(user, handleFavorites)
    }

    const handleSession = (session) => {
        fetchUser(session, handleUser)
    }

    fetchSession("session-id", handleSession)
}

Error Handling in Callbacks Code

Here is the code from the video above:

const mockAPI = (returnValue) => (arg, success, failure) => {
    setTimeout(() => success(returnValue), 2000)
}

const fetchSession = mockAPI({ id: "123765" })
const fetchUser = mockAPI({ firstname: "Bob" })
const fetchUserFavorites = mockAPI([ "lions", "tigers", "bears" ])
const handleError = error => {
    // you can put more custom logic here
    console.log(error)
}

const runCallbacks = () => {
    fetchSession("session-id", session => {
        fetchUser(session, (user) => {
            fetchUserFavorites(user, (favorites) => {
                console.log(favorites)
            }, handleError)
        }, handleError)
    }, handleError)
}

runCallbacks();

Error Handling Recap

The function to handle errors is passed as an argument in the same way we pass a function to handle a response with data Each step in the chain will have its own error handling It is cleaner (especially if needed to do more than console log) to move the error handling out into an external function

Try It Out

Use the Workspace below to run the code from the video. Then change the code in mockAPI to run the failure code to trigger the error handling.

const mockAPI = (returnValue) => (arg, success, failure) => {
    setTimeout(() => failure("Request Failed"), 2000)
}

Cons of Callbacks

We’ve just gotten a look at how to write better callbacks, so you might be thinking callbacks are pretty understandable and great. So why did JavaScript have to add all this other syntax like promises? What was the motivation for creating a new way to do the same thing? In this lesson we’ll explore what has come to be known as Callback Hell and some of the pain points around using callbacks for asynchronous events.

Callback Hell

In the last section we learned some tricks for making callback chains easier to read and debug, but you might have noticed how easily callback chains can get out of hand.

setTimeout( (firstMessage, int) => {
    console.log(firstMessage, int);

    setTimeout((secondMessage, int2) => {
        let sum = int + int2
        console.log(secondMessage, sum);

        setTimeout((thirdMessage, int3) => {
            sum = sum + int3
            console.log(thirdMessage, sum);

        }, 3000, "THIRD", 3);
    }, 2000, "SECOND", 2);
}, 1000, "FIRST", 1);

Imagine if the code block above were not three steps but eight, and each function had five lines of logic to get to the next step, and you were tasked with updating part of the flow. How do you update just one part? What if you need your logic flow to account for multiple different paths? Callback chains were notorious in the JavaScript community as being hairy to read and risky to change.

Also notice how each time we open a new step we have to indent again? Long chains of callbacks end up in this increasingly indented spiral, which is commonly referred to as Callback Hell. Callback Hell along with the low readability made complex callback chains an experience that left many developers frustrated. In vanilla JavaScript there was no way to clean up this syntax or make it easier to deal with asynchronous event chains, but asynchronous flows are essential to JavaScript functionality, so there was no option but to put up with them -- until es2015.

What We Covered

Great job! This lesson was a lot of new terms and concepts, but you've made it through all the exercises and now you're in a great place to pick up promises in the next lesson. Here's a list of the concepts we covered:

  • Threads: single and multi threaded programming
  • Multi threaded concepts of parallel and concurrent programming
  • Blocking and non-blocking concepts
  • Sequencing events with callbacks
  • Callbacks best practices and error handling
  • Cons of callbacks and the need for new syntax

synch programming

Glossary

You have a lot of new terms in your repertoire. Here is a list of the terms covered in this lesson.

Blocking

A process that blocks the thread even when idle or waiting for a response.

Non-Blocking

A process that, while operating (which might be waiting for a response from an external service), allows the thread to move on and continue executing the program. This pattern is essential for efficient use of a thread.

Asynchronous

For managing a single thread. Synonymous with non-blocking in most cases, but can refer to an entire program whereas a program is not typically referred to as non-blocking.

async

Asynchronous = single-threaded, non-blocking

Parallel

For multi-threaded programs ( which will not be JavaScript ). Refers to using two or more threads and running separate processes on them simultaneously.

parallel

Parallel = multi-threaded programs

Concurrent

For multi-threaded programs. Refers to a program that switches between multiple operations. Appears to do many things at once while only operating on one thread at a time.

jsndc3-l1-concurrent.jpg

Concurrent = switching between operations

Thread

Where computers do work. Can also be thought of as a single process. Can do one thing at a time, works linearly through a block of code.

Single Threaded

Meaning a program can only run on one thread. JavaScript will almost always be single threaded.

Multi Threaded

Some languages have the ability to spin up new threads and manage work across multiple threads. Work that takes place across multiple threads is called multi-threaded

Callback Hell

Long chains of callbacks that end up in an increasingly indented spiral.

Sequencing Events - Promises

Concepts

In this lesson we will cover the following:

  • Introduction to Promises
  • Promise chaining
  • Promises best practices and error handling
  • Promise implementations with Fetch
  • Advanced Promise syntax

promises overview

Resources

Here is a list of supporting resources you might find helpful as you start this lesson:

  • The MDN introduction to promises: Graceful asynchronous programming with Promises(opens in a new tab)
  • If you're a pictures person, this site creates visuals for promises: Promisees(opens in a new tab)
  • This article tries to get the basics of Promises into a 5 min read: JavaScript: Learn Promises

Basic syntax

new Promise((resolve, reject) => {
    console.log('A')

    //resolve();
    reject();
})
.then(() => {
    console.log('B');
})
.catch(() => {
    console.log('C');
})

There are a few things I want you to get out of seeing this code right now:

  1. You can run this code and see that A always happens, this base functionality always runs.
  2. B or C display based on whether you comment in the resolve or reject cases - interesting right?
  3. There are some new words here, the ones to pay attention to are: resolve, reject, then, and catch

If the rest of the syntax looks overwhelming or there are lots of question marks going off in your brain - that's ok! This is just step one and I'll explain the concepts above in the next video.

Promise States Recap

promise analogon

A promise starts off with an initial request for something - data, a webpage, or any action that will take place off the main thread. Once we make the request, the promise is in a state called Pending. In other words, whether the request will be successful or fail is unknown and the program is waiting for a response. Promises are asynchronous, so during the pending state, the program will continue on from the promise to complete the rest of the program.

When a response comes back from the request, it will either have succeeded or failed. If it failed, (for example an API response came back with a 500 error) the promise moves into a state called Rejected and the logic held in the .catch() clause will run. If the request succeeds, the promise state is called Resolved or fulfilled, and the logic held in the .then() statement is run. When a promise is no longer pending, whether it is rejected or resolved, it is settled.

These three states of a promise describe the life cycle of a promise and allow us to sequence events in our programs and handle the potential responses from a request.

promises states

Promise Chaining

chaining recap

Promise Chaining Recap

In this lesson we learned that promise chaining refers to building then clauses off of a resolved promise. Here are some of the main points:

  • We can create as many then clauses as we need
  • Then clauses run in the order they are chained
  • If data is returned from the promise resolution, it is passed as an argument to the first then clause
  • If any of the then clauses return data, whatever they return is passed as an argument to the next then clause in the chain
  • If there is an error anywhere in the chain, the chain stops and the catch clause runs.

Code Examples

Here are copies of the code examples from the video in case you want to play with them locally.

// ---------------- PROMISE CHAINING WITH DATA & ERRORS 

new Promise((resolve, reject) => {
    alert('A')
    resolve(['B', 'C', 'D']);
    // reject();
})
.then((data) => {
    // throw new Error('Error at B');
    alert(data.shift());
    return data;
})
.then((data) => {
    throw new Error('Error at C');
    alert(data.shift());
    return data
})
.then((data) => {
    // throw new Error('Error at D');
    alert(data.shift());
    return data
})
.catch((error) => {
    console.log(error)
    alert(error);
})

Promises as the Answer to Callbacks

Now that we have a good idea of what Promises look like and have a feel for how they work, lets consider how they solve the pain points of Callback Hell we discussed in the last lesson.

Benefits of Promises

Callbacks start to get difficult with more complex asynchronous flows. Heavily nested callbacks lead to callback hell, but with promises we have a much cleaner syntax - promise chaining!

What makes promise chaining so appealing? It might not be obvious at first just how big of an advantage it can be. Promise chaining drastically cleans up the syntax of complex asynchronous flows. But rather than explaining, lets show an example. Here is the callback example code you saw in the previous lesson, re-written here with a promises version so you can compare.

Nested Callback Chain

const mockAPI = (returnValue) => (arg, cb) => {
    setTimeout(() => cb(returnValue), 2000);
};

const fetchSession = mockAPI({ id: "123765" });
const fetchUser = mockAPI({ firstname: "Bob" });
const fetchUserFavorites = mockAPI([ "lions", "tigers", "bears" ]);

const runCallbacks = () => {
    fetchSession("session-id", (session) => {
        fetchUser(session, (user) => {
            fetchUserFavorites(user, (favorites) => {
                console.log(favorites);
            });
        });
    });
};

Promise Chain

const mockAPI = (returnValue) => (arg, cb) => {
    setTimeout(() => cb(returnValue), 2000);
};

const fetchSession = mockAPI({ id: "123765" });
const fetchUser = mockAPI({ firstname: "Bob" });
const fetchUserFavorites = mockAPI([ "lions", "tigers", "bears" ]);

const runPromises = () => {
    return fetchSession("session-id")
        .then(session => fetchUser(session))
        .then(user => fetchUserFavorites(user))
        .then(favorites => console.log(favorites))
        .catch(error => console.log("oops!"));
};

Do I Still Need Callbacks?

With all this discussion of how Promises solve the pain points of callbacks, you might begin to wonder if we need callbacks anymore. But we absolutely do! Remember that callbacks are a primary way to chain functionality in JavaScript, so you will still see callbacks around all the time. Like the ES6 Array Methods we discussed in depth in the Functional JavaScript course, or to make use of setTimeout and other built in JavaScript methods, callbacks are an essential part of the language and are not going anywhere. Promises are simply syntactic sugar - meaning that they are just a convenient set of key words that do not add anything fundamentally new to the language. So, where we used to reach for callbacks for everything, we now have another option for longer asynchronous flows.

But are callbacks still a good option for asynchronous code? This comes down mostly to personal taste, but I would say yes. For short and simple asynchronous logic, I think callbacks are still an option. But I think it is safe to say that callbacks should no longer be used for long, complex, or multi-step asynchronous logic chains. That is what Promises were made for, so to not use them would be missing out on the perfect opportunity for cleaner syntax.

promises quiz

promises quiz

Using Promises with Fetch

The Fetch API

JavaScript had an old syntax for making asynchronous XHTTP requests, but you've probably seen the more modern Fetch syntax for making calls to external resources. Being comfortable with Fetch is a singularly helpful skill and absolutely essential for expanding your abilities as a JavaScript developer.

Fetch Request Code Examples

Sometimes its nice to have a reference to important syntax. I am going to walk through an example of a Fetch request performing each of the CRUD actions.

GET

fetch('https://url-with-desired-data')
.then(response => response.json())
.catch(error => console.log(error))

Get type requests are the simplest, the only argument you need to provide to the fetch request is the URL where the data is found. Fetch always defaults to this GET request, but there are other options we can supply to create the other types of requests we might need.

POST / PUT / PATCH / DELETE

fetch('https://url-with-desired-data', {
    method: 'POST', // Other options: PUT, PATCH, DELETE
    mode: 'cors', // Other options are: 'no-cors', 'same-origin', and the default: 'cors'
    headers: {
      'Content-Type': 'application/json'
    },
    body: {"data": "This is json"} // body data type must match "Content-Type" header
  })
.then(response => response.json())
.catch(error => console.log(error))

If you haven't learned about CORS yet don't worry about that part, you will come across it another time, but for now, know that there are more options we could specify here - what I have shown you in this example are just the basic ones that are most common to use and see.

fetch-promise

Promises Best Practices and Error Handling

One of the benefits of using Promises is that by constraining us to a small set of syntax, its easier to keep code simple and readable. The simplicity of the promises syntax means that there aren't a lot of best practice rules you have to remember, but there are still a few things we can do to keep our code clean, and a few examples we can learn from of what not to do.

Functions in Promise Clauses

Resolve and reject clauses have the ability to clean up code drastically, but you can still make them messy if you aren't careful. Avoid the temptation to do too much work in a single clause. For instance, if you find yourself declaring a function inside a clause - you might want to move the declaration outside the promise. If you can identify two distinct things happening in a clause, break it into two clauses. There is no limit to the number of then clauses you can write, so don't feel a need to cram too much logic into one.

Here is code from an earlier example, re-written to have too much logic in a then statement.

const categories = [];
const currentItem = {};

const data = {
    id: "KDF8D903N",
    intVal: 855,
    message: "This is a message",
    sourceId: "NotNull"
}

new Promise((resolve, reject) => {
    resolve(data)
})
.then(data => {
    if (data.sourceId && data.sourceId !== null) {
        return data;
    }
    // when the if statement returns something, there is no need for an else statement
    throw new Error('No source was defined');
})
.then(data => {
    const { intVal, id } = data
    if (intVal > 0 && intVal !== null) {
        const category = data.intVal.toString().split()[0];

        currentItem.id = id;
        category.toString();
        if (!categories.find(category => category.value === id)) {
            categories.push({ value: category, count: 0 })
        } else {
            const index = categories.findIndex(category => category.value === id)
            categories[index].count++
        }
        currentItem.category = category.toString();
        console.log("Category assigned!")
    } else {
        throw new Error('No integer value was provided');
    }
})
.catch((error) => {
    console.log(error)
})

You Need a Catch Clause

One of the biggest ways you can mess up a promise chain is by not creating a catch clause. You haven't seen a single promise without a catch clause in this course yet, because you shouldn't write a promise without one. The problem is that a promise will still try to run without a catch clause, but if an error occurs, you'll get an un-handled rejection error - or worse - you might never see it. Promise chains - depending on where they are run - have the potential to "swallow" errors, meaning that if an error happens and there is no catch to handle it, you might not ever see it. The promise chain will hit the problem, throw an error, but if no one is listening, the program will just move on. This is a common issue I see in professional Node/JavaScript projects.

Here is what happens when you don't catch a promise and it encounters an error. Comment in and out the catch statement and experiment with the two different types of errors in the then clause. The SetTimeout is just a placeholder to mock the rest of your program. Depending on where you run this code you might see a red type error appear, when there is no catch clause, this might or might not appear depending on where you run the code. But, if you have the catch commented in, you will always get a gray console log statement, because the catch is explicitly handling the error.

let num = null;

new Promise((resolve, reject) => {
    resolve(['B', 'C', 'D']);
    // reject();
})
.then(data => {
    foo
    // throw new Error('Error at B');
    console.log(data.shift());
      num = 5;
    return data;
})
.catch(error => console.log(error))
.finally(() => console.log("I'm running!"))

setTimeout(() => {
  console.log("And I'm still running!", num)
}, 5000)
// notice that despite an error in the promise, the code continues running and simply prints the old value of num

Mixing Promise Chain Methods

Take a look at this example:

new Promise((resolve, reject) => {
  resolve("message");
})
.then(() => {
    throw new Error("Something went wrong");
})
.catch(error => {
    return "this is another message";
})
.then(data => {
    throw Error("Now throw another error");
})
.catch(error => console.log(error.message));

Though I don't think this would be considered best practice, it is valid Promise syntax and you might see it around from time to time. To trace what is happening here, an error in a .then statement will cause the nearest catch statement to run. Any .then statement after a catch, is considered further logic on that catch. So in this example, we follow these logic steps:

  1. Resolve the Promise and run the first then statement.
  2. First then statement throws an error which triggers the first catch clause
  3. The first catch clause doesn't throw a new error, it just passes a string to the next then. At this point, by returning a string from a catch clause, we have buried the error that caused the catch to run.
  4. The final then clause runs, and itself throws a new error. This error causes the next catch clause to run
  5. The last catch clause runs and console logs the error message

Better Catch Clauses Recap

In the video we learned that we can handle errors in greater detail while still keeping things neat by writing then clauses off of the catch clause. We also discussed how error handling is important to any app and that we can handle errors better for our users by re-phrasing or creating custom functionality around specific errors JavaScript gives back to us.

Other Resources

Error handling with promises gone wrong: Error handling in long Promise chains

Promises Helpful Tips

Helpful Tips

When working with Promises there are quite a few things that get people confused, even after taking a tutorial - or even if they've been using Promises for a while. Lets take a look at some of these gotchas: Promise chaining across function calls

We've gone through a number of examples now where our then statements were all neatly in a row under the promise. But that is not always how they will be arranged. Often, then clauses will call other functions in our app, or chained then clauses might be separated across many lines or even files. Sometimes beginners get the idea that promise chaining works because the chains are in line with each other, and it can be confusing at first when the chain is broken up throughout the program. So lets see an example and get used to following a promise chain even if its scattered across many functions.

const genericAPIrequest = () => {
   return new Promise((resolve, reject) => {
        resolve({body: "My test data"})
    })
}

genericAPIrequest().then(data => {
    console.log(data);
}).catch(error => console.log(error));

So in the code above, see how the function genericAPIrequest returns a Promise. Any function that returns a Promise, whether its one we make, or that comes as part of a library, or is otherwise abstracted away like with fetch, we have to build our then and catch statements off of the result of that function.

Put another way, the function itself is not a Promise, so we can't do this:

genericAPIrequest.then(data => {
    console.log(data);
}).catch(error => console.log(error));

But rather, we have to call genericAPIrequest and build our then statement off of the result. In your mind, you can replace the genericAPIrequest call with the thing it returns, if we do that, it looks like this:

new Promise((resolve, reject) => {
    resolve({body: "My test data"})
}).then(data => {
    console.log(data);
}).catch(error => console.log(error));

And that looks exactly like what we've been doing all along. This is how it works to chain off of Promises that are returned from other functions, how we dealt with fetch is a good example here. Remember the Return

It might sound silly, but this mistake gets everyone eventually, especially if you are using automatic returns in arrow functions. I myself have fallen into this trap many times, so I'm going to say it here to be very clear: a then statement passes information to the next then in the chain by returning a value. If you don't return anything, your next then statement will try to run without data, which will most likely cause an error and the catch clause will be called.

Here is where this mistake often gets me. I start off with a Promise chain that is using the automatic return rule.

new Promise((resolve, reject) => {
    resolve("This is a message");
})
.then(data => data.split(" "))
.then(data => data[0])
.then(data => console.log(data))
.catch(err => console.error(err));

But then, I need to go back and make a quick edit - maybe even just add a console log to see what is going on in the middle of the chain:

new Promise((resolve, reject) => {
    resolve("This is a message");
})
.then(data => {
  // do some more logic
  data.split(" ")
})
.then(data => data[0])
.then(data => console.log(data))
.catch(err => console.error(err));

And I forget to add the extra return, now this Promise gives me the following error:

error: TypeError: can't access property 0, data is undefined

Any error like this is a good indication that data, or at least not the data you wanted, is being passed all the way through the chain.

So here is the code with the fix:

new Promise((resolve, reject) => {
    resolve("This is a message");
})
.then(data => {
  // do some more logic
  return data.split(" ")
})
.then(data => data[0])
.then(data => console.log(data))
.catch(err => console.error(err));

At least its a quick fix! This mistake is easy to make and easy to skip over when you've been looking at the same code for a while, but now you know what kind of error to watch out for.

Promise Practice Solution

Here is one solution for the challenge:

const eventMessage = JSON.stringify({body: "Your account has been updated!"});
const currentUser = JSON.stringify({
    name: "Ralph S. Mouse",
    id: "238jflK3"
});

const getUserInformation = () => {
    return new Promise((resolve, reject) => {
        setTimeout(resolve, 2000, currentUser);
    }).then(data => JSON.parse(data))
};

const getEventMesssage = () => {
    return new Promise((resolve, reject) => {
        setTimeout(resolve, 2000, eventMessage);
    }).then(data => JSON.parse(data))
};

getUserInformation().then(userInfo => {
    getEventMesssage()
    .then(message => message.body)
    .then(message => {
        const greeting = `Thank you, ${userInfo.name}.`
        console.log(`${greeting} ${message}`)
    })
}).catch(err => console.log(err));

There are a few things you can note here. First, if you are making an API request, it is smart to change the response into the format you need it as soon as possible. So in this case we are receiving JSON and we want a JavaScript object, so I decided to make that conversion as soon as I got the response back. It will work either way, but its easier to remember when you do those things immediately.

Another thing that might have stumped you on this exercise is that this is the first time we are dealing with nested Promises. Here, we can't print the message until both the user information and message are available, that's why we had to make the second request in the then statement of the first Promise.

Advanced Promise Syntax

We've covered a lot of ground with promises already - but there's still more to learn! In this lesson we will cover some additional syntax that will streamline our promise flows and open up a whole other level of control when handling events. Let's get started!

Promise Methods Part 1

Promise Methods Part 1 Recap Here are the methods we went over in the video.

New Promise
new Promise((resolve, reject) => {
// can do logic here
// resolve() or reject()
}

Promise new initiates a new promise. From here you can resolve or reject the new Promise you have made.

Promise.reject

Called in a Promise, will cause the .catch clause to run in the same way an error does.

Promise.resolve

Called in a Promise, will cause the .then clause to run. Can optionally be passed an argument that will go to the next statement.

Promise.finally

Like .then or .catch, finally is another clause that runs after the other clauses. It runs in both a reject or resolve case.

Promise.allSettled

Promise.allSettled takes an argument that is an array of Promises. It waits for all those Promises to move to the settled state and collects the results of each one into a new array of results. The resulting array contains one object per Promise, saying whether that Promise resolved or rejected, along with the value (if it resolved) or the reason for failure (if it rejected). Promise.allSettled itself returns a Promise, so the resulting array is available in the following .then statement.

allsetteld

const book1 = new Promise((resolve, reject) => {
    setTimeout(resolve, 3000, "Enders Game");
});

const book2 = new Promise((resolve, reject) => {
    setTimeout(reject, 4000, "Sorry, not available!");
});

const book3 = new Promise((resolve, reject) => {
    setTimeout(resolve, 2000, "Harry Potter and The Prisoner of Azkaban");
});

const book4 = new Promise((resolve, reject) => {
    setTimeout(resolve, 1000, "Stranger in a Strange Land");
});

Promise.allSettled([book1, book2, book3, book4])
.then(results => {
    console.log(results)
    results.forEach(result => console.log(result.value))
})
.catch(error => console.log(error));
Promise.all

This method is almost exactly the same as Promise.allSettled except for what it returns and how it handles rejected Promises. It takes in an array of Promises and waits for them to resolve, but the first time it encounters a rejected Promise, it stops waiting for any further Promises and runs its catch clause. If no Promises reject, it returns an array of the values returned by them. Again like Promise.allSettled, Promise.all returns a Promise, so the resulting array is available in the following then.

promiseAll

const book1 = new Promise((resolve, reject) => {
    setTimeout(resolve, 3000, "Enders Game");
});

const book2 = new Promise((resolve, reject) => {
    setTimeout(reject, 4000, "Sorry, not available!");
});

const book3 = new Promise((resolve, reject) => {
    setTimeout(resolve, 2000, "Harry Potter and The Prisoner of Azkaban");
});

const book4 = new Promise((resolve, reject) => {
    setTimeout(resolve, 1000, "Stranger in a Strange Land");
});

Promise.all([book1, book2, book3, book4])
.then(results => {
    console.log(results);
    results.forEach(result => console.log(result.value));
})
.catch(error => console.log(error));
Promise.race

Promise.race also takes an argument of an array of Promises, but instead of waiting for them all to resolve, it only waits for the fastest one. Whatever Promise fulfills first, whether is resolves or rejects. It will pass the value from the resolution or the error from the rejection to its then statement.

Promise Race Catch

const book1 = new Promise((resolve, reject) => {
    setTimeout(resolve, 3000, "Enders Game");
});

const book2 = new Promise((resolve, reject) => {
    setTimeout(reject, 4000, "Sorry, not available!");
});

const book3 = new Promise((resolve, reject) => {
    setTimeout(resolve, 2000, "Harry Potter and The Prisoner of Azkaban");
});

const book4 = new Promise((resolve, reject) => {
    setTimeout(reject, 1000, "Sorry, not available!");
});

Promise.race([book1, book2, book3, book4])
.then(result => {
    console.log(result);
})
.catch(error => console.log("Error!", error));

Lesson Glossary

Promise

A JavaScript Promise object is essentially a placeholder for a value that is not available immediately. A promise is asynchronous and represents the result of a request or operation that may succeed or fail.

Promise States

JavaScript Promises have four states. When a promise has been initiated but the result has not yet come back, the promise is PENDING. A promise that succeeded is RESOLVED and a promise that failed is REJECTED. When a promise is either rejected or resolved, its state is SETTLED, which simply means that it is no longer pending, regardless of the result.

Fetch

Fetch is the syntax for a modern promise based HTTP request in JavaScript.

Quick Syntax Reference

New Promise
new Promise((reject, resolve) => {
  // Make your initial request
  // Promise is now PENDING 
})
Resolved Promise Logic
.then(data => {
  // Promise is now SETTLED and RESOLVED
  // Your logic here
  // Return a value you want to pass to the next then statement
})
Rejected Promise Logic
.catch(error => {
  // Promise is now SETTLED and REJECTED
  // handle the error
})
Fetch
fetch(url, options)
.then(data)
.catch(error)