JavaScript Promises

Notes by James Priest

JavaScript Promises

<– back to Mobile Web Specialist Nanodegree homepage


Articles

Docs

Lesson 1. Creating Promises

1.1 Course Introduction

Hi everybody, I’m Cameron Pittman. Welcome to this short course on JavaScript promises.

prom1-1

Every web developer needs to be able to handle asynchronous work with confidence and ease, and that’s why you’re here. You’re here to take advantage of native JavaScript promises.

There are many methods to handle asynchronous work already. However, as you’ll be discovering in this course, promises are the recommended option, because they give you flexibility,intuitive syntax, and easy error handling.

But before we start all that, I have an off topic question for you.

You’re looking at millions of stars in the disk of our Milky Way galaxy.

prom1-2

Every dot is a star. Some are bigger and brighter than the sun, others smaller and dimmer. But what else is in this picture? Planets.

Every single star in the Milky Way is the possible home for dozens of alien worlds. That means it’s likely that there are hundreds of billions of planets in our galaxy alone.

The Kepler Space Observatory has found more than 1,000 exoplanets like this one in the last few years. Combined with data from other telescopes, astronomers have confirmed sightings of almost 2,000 exoplanets.That’s a lot of worlds.

I had an idea when I was designing this class, andI call it the Exoplanet Explorer. It’s a web app that helps people learn about real planets around other stars.

prom1-3

Throughout this class, you’ll be using promises to request real JSON data about exoplanets, and that data is coming from NASA and CalTech.

You’ll be using promises to turn that data into useful information in the app. Are you ready to get started?

1.2 Sync vs Async

“The Promise object is used for deferred and asynchronous computations.”
-MDN

Okay, great. This statement is less than helpful if you’re understanding of the word asynchronous is less than certain.

So, the question is, what is asynchronous work?

Asynchronous work happens at an unknown or unpredictable time.

prom1-4

Normally code is synchronous. One statement executes, like this one, and there’s a guarantee that the next statement executes immediately afterwards.

prom1-5

Threading on processors doesn’t necessarily guarantee that, however, the JavaScript threading model ensures that, for all intents and purposes, JavaScript runs in a single timeline.

Unlike synchronous code. Asynchronous code is not guaranteed to execute in a single unbroken timeline.

prom1-6

In fact, you should assume that you have no idea when asynchronous operations will complete.

In a situation like this, for example, you can’t even assume that just because you sent out the first request first and the second request second, that the first request will return sooner than the second.

Assume that either order is equally likely, as the fact that one or both requests just might fail altogether.

Network requests aren’t the only examples of asynchronous code. Any code that relies on processes like these, like events, like threads, or just some kind of unknowable finishing time are asynchronous.

prom1-7

So, the big question is, what is the best way to handle asynchronous code?

Okay, the answer is obviously promises. That’s why you’re here.

1.3 Callbacks vs Thens

Callbacks are the default JavaScript technique for asynchronous work. Pass the function to another function, and then call the callback function at some later time when some conditions have been met.

prom1-8

This works well but there are some questions without obvious answers. For instance, how do you handle errors?

It’s best practice to assume that any operation could fail at any time. It’s doubly important to assume so with network requests.

prom1-9

If an error occurs on line #2, should you still execute the call back? If so, what value should it receive? If not, then what should happen?

What if it’s a JavaScript error? Should that be handled any differently than a network error? There’s no clear answer.

Node.js basically makes error first callbacks mandatory but that doesn’t really solve the problem. It’s still basically your job to define, and implement an error handling strategy.

Assume that everything happened just fine, no errors and then this callback runs when the onload handler gets called.

prom1-10

Great. Now you’ve chained together two pieces of work, but what if this callback is also an asynchronous operation and you need something else to happen afterwards? Do you pass another function with another callback here?

This is one scenario that leads to something called the Pyramid of Doom. A dreadful situation where there are nasty, nasty nested callbacks within callbacks, within callbacks.

prom1-11

This looks ugly and it’s hard to write, but the real sin is that it is incredibly frustrating to debug.

This is the same kind of code, but written with promises, and my, it looks nicer.

prom1-12

You’ll be learning all about the beauty of .then later in this course. For now, just read this and appreciate the fact that you can figure out what’s happening here, despite the fact that you haven’t even finished this course.

That’s pretty awesome.

1.4 Course Map

I broke this course into four stages that built on one another.

prom1-13

You’ll start in the Wrapping stage where you’ll be learning the syntax of constructing promises.

A promise constructor in and of itself isn’t super useful. So from there, you’ll be learning how to react the resolution of the promise.

If all goes well, you’ll want to .then off the promise, or if something breaks, you’ll want to .catch the error, these are the Thening and the Catching stages.

You’ll spend the whole second lesson in the Chaining stage where you’ll learn how to create long sequences of asynchronous work.

It’s also worth going over some promise vocabulary. I’m stealing these definitions from Jake Archibald who wrote a fantastic guide on promises that you can find in the Resources section below.

There are four states a promise can have. You’ll pick up these terms pretty quickly, but I recommend jotting them down on a piece of paper to help out as you’re going through the course.

prom1-14

Here they are.

The first one is fulfilled, and that means that the action related to the promise succeeded, this state is also known as resolved.

Then there is rejected, this means that the action related to the promise failed.

The next one is pending, which means that the promise has not yet fulfilled or rejected.

And then finally, there’s settled, which means that the promise has either fulfilled or rejected.

Okay, now that you’ve got the terms behind promises and you know where we’re going in the course, it’s time to examine how and when promises are executed.

Resources

1.5 Promises Timeline

I want you to imagine a situation like this, where you’re setting an Event listener after the event has already fired.

prom1-15

What happens? Nothing.

If the event doesn’t fire again, the Event listener never gets called.

Now imagine you’re using promises and you set an action to occur for when a Promise resolves which is set after the promise has already resolved.

prom1-16

Guess what? This will execute.

Compare this to Event listener example where the Event listener would never get called if it’s set after the event already fired.

prom1-17

I want to quickly show you some code.

This is a promise constructor, and you’ll be learning more about this in the next video. This method resolve(), settles the promise.

prom1-18

A promise can only settle once. So in this scenario, the second resolve is doing nothing. Nothing’s going to happen, it’s going to go by unnoticed.

Compare this to events. An event can fire many times, but a promise can only settle once.

Promises execute in the main thread, which means that they are still potentially blocking.

prom1-19

If the work that happens inside the promise takes a long time, there’s still a chance it could block the work the browser needs to do to render the page.

If so, the frame rate of your app is going to suffer, and you’ll probably hear your users complain about jank.

Promises are not a pass for safely executing long running operations. They’re simply a technique for deciding what will happen when an asynchronous task settles.

Think of them as try...catch wrappers around asynchronous work.

Okay, it’s time for a quiz.

1.6 Quiz: Async Scenarios

In which of these situations should you consider using Promises?

prom1-20

  1. Should you consider using Promises when you’re working with information from an Ajax request?
  2. Should you use them when you’re executing long-running image manipulation in the main thread?
  3. Should you use them when you’re creating a series of divs and appending them to the body in a specific order?
  4. Should you use them when you’re posting messages back and forth between the main thread and a web worker. If you’re not familiar with web workers, it’s an API that allows you to create independent threads to execute long running JavaScript off the main thread.

There’s more than one answer, so check all that apply.

Solution

So, which of these scenarios are right to be promisified? I’ll go ahead and start at the top.

  1. Working with information from ajax requests.

    Most definitely. The word asynchronous is in the very definition of ajax, and you’ll be working with these kinds of requests soon.

  2. Executing long running image manipulation work in the main thread.

    No. Promises run in the main thread, so you don’t gain anything from wrapping work in the main thread in a promise. The work will still happen synchronously and still probably lead to a janky experience.

  3. Creating a series of HTML elements.

    No. Creating and appending DOM elements is synchronous, so there’s no need to wrap them in promises. That being said, if these were, say image elements, or script elements with links to external resources, then loading the external resources themselves, are asynchronous operations. You could use promises to chain actions after the resources load. Likewise, if the data came from an asynchronous resource, then you could wrap this in a promise, but that’s not what’s happening here.

  4. Posting messages back and forth between the main thread and a web worker.

    Yes. Web workers run on separate threads and post data to the main thread. They are certainly asynchronous and perfect for promises.

All right, that’s enough discussion about promises. It’s time to start writing them.

1.7 Promise Syntax

We are entering the wrapping stage. I want you to keep in mind that:

prom1-21

Here, let me show you an example.

Promise is a constructor. You can either store a promise as a variable like I’m doing right here, or you can simply work on it as soon as you create it.

prom1-22

Either way works just fine, but you’ll often see me simply work on the promise without storing it as a variable.

ES5

Here’s the code from above.

var promise = new Promise(function(resolve, reject) {
  var value = doSomething();
  if(thingWorked) {
    resolve();
  } else if (somethingWentWrong) {
    reject();
  }
}).then(function(value) {
  // success!
  return nextThing(value);
}).catch(rejectFunction);

ES6

This is the same promise constructor written in ES6.

const promise = new Promise((resolve, reject) => {
  const value = doSomething();
  if(thingWorked) {
    resolve();
  } else if (somethingWentWrong) {
    reject();
  }
}).then(value => {
  //success!
  return nextThing(value);
}).catch(rejectFunction);

You pass a function to the promise with two arguments: resolve and reject.

prom1-23

resolve and reject are the two callbacks you use to specify when a promise has either fulfilled, because something worked, or rejected because something went wrong.

Let me show you what this actually looks like.

In this example, I’m wrapping an image tag loader in a promise because I want to do some work after the image loads on a page.

prom1-24

I’m using the image tag’s onload handler to specify success.

onload calls resolve, which queues up the function passed to .then to execute after the remainder of the code in this function block finishes executing.

Note: the JavaScript engine does not immediately stop executing this function upon calling resolve.

It will execute the remaining lines in the function before transferring control to .then which in turn executes finishLoading.

An example of this concept in action can be found in the Resources section below.

Being able to call resolve and reject is important. This is what it gives you the flexibility to explicitly say what constitutes fulfillment and what constitutes rejection for the promise.

When either resolve or reject has been called, the promise has been settled, and then at that point, the next part of the chain, usually, a .then, or it could be a .catch, is executed.

Back to the original example, any value passed to resolve or reject will be received as an argument by this subsequent .then or .catch.

prom1-25

In the event that nothing is passed to resolve or reject as is the case here with the reject, then it’s totally fine. The next link in the chain simply receives undefined.

prom1-26

Note: Just to be clear, when passing values or undefined through resolve() and reject() to .then and .catch, the values themselves aren’t being passed to .then or .catch, rather they’re being passed to the functions called by .then or .catch.

There’s a third case and that is if the value that’s passed is a promise.

If so, then the promise will execute first and then whatever value it resolves to will be passed to the next link in the chain.

prom1-27

Note that resolve and reject have the same syntax. resolve leads to the next .then in the chain, while reject leads to the next .catch.

Incidentally, if there is a JavaScript error somewhere in the body of the promise, .catch will also automatically get called.

And by the way, there’s a bit more to error handling and you’ll be learning about it in the next lesson. In the meantime, it is time for you to write your first promise.

Resources

Sample code that shows the remainder of the code in the function block gets executed even after resolve() gets called.

new Promise(function(resolve) {
  console.log('first');
  resolve();
  console.log('second');
}).then(function() {
  console.log('third');
});

You’ll notice that 'first', 'second' and 'third' all get logged. Most notably, 'second' gets logged despite the fact that it comes after resolve().

1.8 Quiz: Write a Promise

For this quiz you’ll be wrapping setTimeout() with a Promise. There won’t be any error handling yet because I want to keep this first quiz simple.

prom1-28

I’m giving you a sample page to work with, and inside of it you’ll find a function called wait(). This function should simply wait a set number of milliseconds before resolving.

That means that you’ll need to call resolve when setTimeout() executes its callback.

While you’re working on this function, I want you to console.log(this) inside the Promise. The reason is that I have a question for you about the scope of this inside Promises when you finish working on it.

I also want you to return the Promise from wait. The reason is that, after you finish your work, you will be able to uncomment these two lines to test your code.

prom1-29

If there are no errors, and the text changes, you’ll know you’ve done it right.

And like I said, when you’re done, I have a question for you:

Resources

Instructions
  1. Download setTimeout-start.zip from the Supporting Materials Section.
  2. Wrap setTimeout in a Promise inside the wait() function. resolve() in setTimeout’s callback.
  3. console.log(this) inside the Promise and observe the results.
  4. Make sure wait() returns the Promise too!
Supporting Materials

Solution

So here’s the solution. To start off I’m creating a Promise by using new Promise().

prom1-30

I’m going to pass a function into the Promise with the parameter resolve. There is no reject in this case because I’m never going to need to reject so I just don’t include it.

Inside the Promise I’m logging this so I can check out its scope later.

You’ll see that in the callback function to the setTimeout(), I’m calling resolve() to resolve the Promise.

In this case the Promise will resolve after some number of milliseconds that’s getting passed into wait().

Also notice that I am returning the Promise and you’ll see why in a second.

Then I’ve un-commented these two lines which say that after 2000 milliseconds the finish function should get called.

prom1-31

All right, let’s see how this looks. I will go ahead and refresh the page.

You see that two seconds later it is complete.

prom1-32

It looks like this logged out the window, or the global object.

Note: the scope of this, could change depending on the context.

For instance, arrow functions with ES2015 take this from the enclosing execution context. Whereas in ES5, every new function defines its own this value.

See MDN article on Arrow functions for a complete explanation.

So the correct answer is the ‘global object’.

ES6 Solution

Here is the same code written using ES6 syntax.

function wait(ms) {
  return new Promise(resolve => {
    console.log(this);
    window.setTimeout( () => {
      resolve();
    }, ms);
  });
};

var milliseconds = 2000;
wait(milliseconds).then(finish);

// function to test with
function finish() {
  var completion = document.querySelector('.completion');
  completion.innerHTML = "Complete after " + milliseconds + "ms.";
};

This can be further shortened to:

function wait(ms) {
  return new Promise(resolve => {
    console.log(this);
    window.setTimeout( resolve, ms);
  });
};

var milliseconds = 2000;
wait(milliseconds).then(finish);

// function to test with
function finish() {
  var completion = document.querySelector('.completion');
  completion.innerHTML = "Complete after " + milliseconds + "ms.";
};

1.9 Quiz: Wrap & Chain

Okay, that first quiz was a little bit on the simple side. This one’s going to be more useful.

You’ll be replicating jQuery’s .ready() feature by wrapping a check for document.readyState in a Promise.

jQuery Sidenote

While we won’t be using jQuery for this exercise, I did want to note the new recommended syntax for calling jQuery’s .ready() method.

Note: The following syntaxes are all equivalent ways of attaching a function to run when the DOM is ready.

  • $( handler )
  • $( document ).ready( handler )
  • $( 'document' ).ready( handler )
  • $( 'img' ).ready( handler )
  • $().ready( handler )

As of jQuery 3.0, only the first syntax is recommended.

Deprecated (as of jQuery 3.0)
$(document).ready(function() {
  // Handler for the .ready called  
});
// es5
$(function() {
  // Handler for .ready() called
});

// es6
$(() => {
  // Handler for .ready() called
});

Thening

You are now in the thening stage of the course.

prom1-33

I want you to use .then to perform an action after a promised result.

document.readyState has three possible states: loading, interactive, and complete.

prom1-34

  1. loading - the document is still loading.
  2. interactive - the document has loaded and been parsed but sub-resources like images and style sheets have yet to load. This is equivalent to the DOMContentLoaded event.
  3. complete - all the sub-resources including images and style sheets have loaded.

Every time the document’s readyState changes, a readystatechange event fires.

prom1-35

Creating a promise to run on interactive is really useful if you want to run some code as soon as all of the initial DOM elements have been loaded.

For this quiz I’m giving you an event handler for readystatechange.

prom1-36

I want you to wrap it in a promise so that it resolves when the DOM is ‘interactive’. Or, in other words, I want it to resolve when the readyState is no longer ‘loading’.

Like the last quiz, you don’t need to worry about error handling because if the DOM is never ready then the page won’t display anyway.

Make sure you test, too. Do so by using .then to chain the method wrapperResolved when the document is ready.

prom1-37

Make sure you check out the Instructor Notes forhelp getting started, and good luck.

Resources

Instructions
  1. Download readyState-start.zip in the downloadables section.
  2. Set network throttling so that the page isn’t ready instantly. (Also, it’s generally a good practice to have some throttling when testing sites. It’ll help you see your site’s performance from your users’ perspectives.)
  3. Wrap an event listener for readystatechange in a Promise.
  4. If document.readyState is not 'loading', resolve().
  5. Test by chaining wrapperResolved(). If all goes well, you should see “Resolved” on the page!
Supporting Materials

Solution

To start off, I need to give credit to Jake Archibald, who came up with the idea for this quiz, and who also wrote this code.

prom1-38

There are two parts to the ready() method.

  1. It checks the readyState when the readystatechange event fires
  2. And it also checks the readyState immediately.

By checking immediately, the ready() method will still work if readyState becomes ‘interactive’ before this promise is created, so that’s pretty useful.

And if the readyState is still loading when the promise is created, it’ll call checkState every time the readystatechange event fires.

Once the readyState is no longer ‘loading’, it resolves.

So here’s how I test it. I simply call ready(), and then chain, .then(wrapperResolved) to the end.

Here’s the completed code:

function ready() {
  return new Promise(resolve => {
    function checkState() {
      if(document.readyState !== 'loading') {
        resolve();
      }
    }
    document.addEventListener('readystatechange', checkState);
    checkState();
  });
};

ready().then(wrapperResolved);

// function to test with
function wrapperResolved() {
  var completion = document.querySelector('.completion');
  completion.innerHTML = "Resolved!";
};

Time to see how it looks. To test with I’m using 3G so that the image takes a little bit to load.

Remember the text should say “Resolved!” before the image shows up.

prom1-39

Now, it already say resolved but let see what happens when the page get refreshed. You can see that its resolved immediately before the image even finishes loading, that’s pretty cool.

Okay. Now it’s time to try some error handling.

1.10 Install Exoplanet Explorer

What is this?

You’ll be working with a stripped down version of the Exoplanet Explorer to complete all of the programming quizzes for the rest of the course. So, you need to install it.

Installation

I built the Exoplanet Explorer from the Polymer Starter Kit. I’m copying most of the rest of these instructions from the README written by the Polymer team. If you ever need help, check out the README.

Clone the repo first (for everyone)

Here’s the link to the repo.

You should be on the xhr-start branch. If not, then git checkout xhr-start or git checkout origin xhr-start.

Quick-start (for experienced users)

With Node.js installed, run the following one liner from the root of your Exoplanet Explorer download:

npm install -g gulp bower && npm install && bower install
Prerequisites (for everyone)

The full starter kit requires the following major dependencies:

To install dependencies:
  1. Check your Node.js version.

     node --version
    

    The version should be at or above 0.12.x.

  2. If you don’t have Node.js installed, or you have a lower version, go to nodejs.org and click on the big green Install button.

  3. Install gulp and bower globally.

     npm install -g gulp bower
    

    This lets you run gulp and bower from the command line.

  4. Install the starter kit’s local npm and bower dependencies.

     cd exoplanet-explorer && npm install && bower install
    

    This installs the element sets (Paper, Iron, Platinum) and tools the starter kit requires to build and serve apps.

NOTE! INSTALLATION MAY TAKE A LONG TIME! There are many dependencies to download and install.

Post-Installation and Workflow

For every quiz, you’ll be given a branch to checkout. You can always find it in the instructor notes. All of your work will be done in:

app/scripts/app.js
Serve / watch
gulp serve

This outputs an IP address you can use to locally test and another that can be used on devices connected to your network.

It’s recommended to use Chrome, as non-vulcanized Polymer projects will load fastest on Chrome.

Build & Vulcanize
gulp

Build and optimize the current project, ready for deployment. This includes linting as well as vulcanization, image, script, stylesheet and HTML optimization and minification.

Troubleshooting

See this bug if you get the following error from Gyp:

"Error: self signed certificate in certificate chain"

1.11 Quiz: Wrap XHR

I hope you’re starting to get the hang of Promises. I’ve got another challenge for you.

I want you to wrap an Ajax request in a Promise. You’re now officially in the catching stage, and don’t worry, it’s pretty similar to the thening stage.

prom1-40

In the instructions I’ll be asking you to add .catch to your promise as a way to see if there are any errors, and then do something about it.

Right now you won’t be recovering. You’ll simply be console.logging them. To take this quiz, you’ll be doing your work here, inside the Exoplanet Explorer app.js file.

prom1-41

I want you to wrap this XHR helper method with a Promise.

Unlike the last two quizzes, a lot can go wrong with a XHR so you need to include error handling: reject() if there’s an error, or if the request status is anything but 200.

Remember, any value that you pass to resolve() or reject() will get passed to the next .then or .catch, and of course, don’t forget to return the promise from get().

Test your Promise wrapper with the get() method (shown as commented out).

prom1-42

The file that’s requested contains a simulated search response for the exoplanet explorer app, and it contains the URLs for some JSON data of some Earth-like planets.

Pass the response to addSearchHeader() when it resolves, or if it fails to resolve, catch it by passing ‘unknown’ to addSearchHeader() on reject.

You might try messing up this URL to make sure your .catch works correctly.

While you’re working, I recommend using gulp serve to watch for changes and automatically reload the page.

This is definitely the fastest way to work.

Resources

Instructions
  1. Checkout the xhr-start branch and navigate to app/scripts/app.js.

     git checkout xhr-start
    
  2. Wrap the XHR in a Promise in the get() function. See XHR documentation for more information.
  3. Resolve on load and reject on error.
  4. If the XHR resolves, use addSearchHeader() to add the search header to the page.
  5. If the XHR fails, console.log() the error and pass ‘unknown’ to addSearchHeader().

The solution is on the xhr-solution branch.

git checkout xhr-solution

Solution

To start off I’m calling gulp serve, and letting that run in the background.

prom1-43

So here’s my promise wrapper inside get().

prom1-44

Notice that I’m returning the promise, and I’m resolving when the request status is 200. I’m passing the response to resolve so that then gets the response as well.

I’m rejecting on any error whether that’s not a status of 200 or something else, and the rest of it looks good. I didn’t have to change anything else.

Down here in the web components ready listener, I am actually doing something to the page.

prom1-45

Once I get the data, I add the search header, and if something goes wrong, I add a search header of ‘unknown’ and then console that log error.

Okay, let’s see what happens.

prom1-46

So it looks like this query works because you can see ‘earth-like planets’ on the page.

But let me see what happens when I mess up that URL. I’ll go ahead and just delete a character.

prom1-47

Okay, that looks good. I see ‘unknown’ as the query and I see an error here in the console. I can open it up and see that something went wrong with the XHR.

Here’s the completed code:

function get(url) {
  return new Promise(function (resolve, reject) {
    var req = new XMLHttpRequest();
    req.open('GET', url);
    req.onload = function() {
      if (req.status === 200) { // It worked!
        resolve(req.response);
      } else { // It failed :(
        reject(Error(req.statusText));
      }
    };
    req.onerror = function(error) { // It failed :(
      reject(Error('Network Error'));
    };
    req.send();
  });
}

window.addEventListener('WebComponentsReady', function() {
  home = document.querySelector('section[data-route="home"]');
  get('../data/earth-like-results.json')
    .then(function (response) {
      addSearchHeader(response);
    }).catch(function (error) {
      addSearchHeader('unknown');
      console.log(error);
    });
});

Cool, I want to show you one more thing though. Inside the addSearchHeader() there is this call to JSON.parse of the response.

prom1-48

If you think about it, parsing a JSON really has nothing to do with adding a search header, so in the next quiz you’ll be removing this all together and putting it inside a different method where it does belong.

1.12 Web Technologies

The course that you’re taking is about native JavaScript promises. They started showing up in browsers around the end of 2014, but polyfills for promises have been around for much longer actually.

jQuery, which is obviously a very popular library, implements its own version of promises, but prior to v3.0 they had some serious issues. So I definitely recommend reading up on them in the Resources section if you need to use them.

The first version of Angular uses Q style promises. These are mostly the same as native promises, but have some slight differences.

Angular 2, however, does actually take advantage of native JavaScript promises.

As of January of 2016, native promises are safe to use with every major browser, except for Internet Explorer and Opera Mini. You’ll need to make sure that you include a polyfill or some other kind of fall-back on your production sites.

prom1-49

There are some new APIs that are also taking advantage of promises. For instance, they’re the recommended strategy for interacting with the Service Worker API.

Service workers are a total game changer. They allow you to add a powerful layer of control between your app and the network, and that means that you can actually create apps that work offline.

In the next quiz you’ll be using another new API called the Fetch API. It uses native promises to simplify xml http requests.

Resources

jQuery Promises
Q Style Promises
Browser Implementation
APIs that Use Promises

1.13 Quiz: Fetch API

As you probably realized a few videos ago, or maybe even longer, XHRs are really annoying.

Even the simplest use case is fairly verbose, and it only gets worse as you address edge cases and browser compatibility issues.

Fetching resources really just shouldn’t be this hard. Luckily there’s a new API that simplifies XHRs, the Fetch API.

prom1-50

Currently it’s natively supported by all browsers except IE and Opera Mini. It’s incredibly useful and also it’s built on native promises.

This is going to be a two step quiz. On the first part you’re going to re-factor get() with the Fetch API and in the second part, you’re going to be creating a new wrapper called getJSON().

prom1-51

getJSON() should return a Promise and it should return a promise that’s two steps.

The first part should be getting the URL, you should use the get() method for that, and the second part should be parsing the JSON response.

I want you test getJSON() by adding a search header and console.log the parsed JSON.

prom1-52

When you do so, you should see an object with search data appear in the console.

If there is an error, catch it. Pass ‘unknown’ as the search header and then console.log the error.

prom1-53

Resources

Instructions
  1. Checkout the fetch-start branch and navigate to app/scripts/app.js.
     git checkout fetch-start
    
  2. Rewrite get() with the Fetch API: https://davidwalsh.name/fetch
  3. Finish the getJSON() method, which should take a URL and return the parsed JSON response.
    • getJSON() needs to return a Promise!
  4. Test by logging the response and by passing the query string from getJSON() to addSearchHeader().
  5. Handle errors by passing ‘unknown’ to addSearchHeader() and logging them.

The solution is on the fetch-solution branch.

git checkout fetch-solution

Solution

So this is how I did it. Inside get I am just using fetch, in fact, this whole object here with method get is totally optional.

prom1-54

If there are no options fetch assumes that you’re doing a get, so you can just get rid of it.

function get(url) {
  return fetch(url);
}

In getJSON(), I simply .then off the get() and then return response.json.

This response.json is specific to the fetch API, but it’s basically the same as JSON.parse.

To test, I’m getting the json and then if it’s there, I add the query to the search header and console.log the response.

If it’s not there, I add ‘unknown’ to the search header and console.log the error.

prom1-55

Let’s see if it works.

So far so good, I’m seeing earth-like planets and when I open up the console, I see the response logged out with the query and some more JSON files.

prom1-56

That’s pretty cool but I want to show you one more trick. I just added another .then to output the first json url.

getJSON('../data/earth-like-results.json')
  .then(function (response) {
    addSearchHeader(response.query);
    console.log(response);
    return response.results[0];   // <-- here
  }).then(function(url) {         // <-- here
    console.log(url);             // <-- here
  }).catch(function (error) {
    addSearchHeader('unknown');
    console.log(error);
  });

So from the original .then, I am returning some results. In fact, this is the first URL in the search results.

Then, in the next .then, I am taking the URL and console.logging it. The cool thing here is that I’ve got .then and then another .then and then finally a .catch, which means I’m chaining promises together.

So now inside the console, after the whole search object, I’m seeing the URL of the first planet in the response.

prom1-57

This whole idea of chaining is incredibly important, and that’s what you’ll be doing next in the course.

Here’s the complete code.

function get(url) {
  // Use the Fetch API to GET a URL. Return the fetch.
  return fetch(url);
}

function getJSON(url) {
  // Return a Promise that gets a URL and parses the JSON response. Use get().
  return get(url).then(function(response) {
    // Handle network errors
    if (!response.ok) {
      throw Error(response.statusText ? response.statusText
        : 'Unknown network error')
    }

    return response.json();
  });
}

window.addEventListener('WebComponentsReady', function() {
  home = document.querySelector('section[data-route="home"]');
  // Don't forget to chain with a .then and a .catch!
  getJSON('../data/earth-like-results.json')
    .then(function(response) {
      addSearchHeader(response.query);
      console.log(response);
      return response.results[0];
    }).then(function(url) {
      console.log(url);
    })
    .catch(function(error) {
      addSearchHeader('unknown');
      console.log(error);
    });
});

1.14 What’s Next

So far you’ve created promises and chained the some work off them.

prom1-58

You did so in the last quiz by creating a get() function and then using .then to do something with the data that you retrieved.

You also included catches to ensure that no error goes unhandled.

With the promise API, .then’s also return promises.

So if you can .then off of an initial promise, you can .then off of .thens because they are also promises.

Developers commonly use the term ‘thenable’ to describe promises and .thens.

In fact, other libraries include thenable objects. See below for more information.

When creating a chain of asynchronous work, each subsequent link in the chain receives either the fulfilled value of the previous promise or the return value of the previous .then’s function.

In this way, you can pass information collected from one asynchronous method to the next.

Being able to chain thenables is an incredibly powerful technique for simplifying complex sequences of asynchronous work.

You’ll be practicing a few chaining strategies in the next lesson.

So this is the end of the first lesson. You have already added a powerful new tool to your toolbox, and you are just getting started.

At the beginning of the next lesson you’ll be creating a chain of .thens to put thumbnails on the Exoplanet Explorer.

Resources

Lesson 2: Chaining Promises

2.1 Quiz: Fetch & Show First Planet

Welcome back. In this lesson you’ll be experimenting with different techniques for creating chains of asynchronous work.

prom2-1

Asynchronous work is rarely isolated, and as such you may have many asynchronous actions that depend on one another.

This is another strong suit of promises.

Rather than creating a pyramid of doom, promises make it straightforward to chain together lots of asynchronous actions. It even works when you’re generating those actions programmatically.

To get started, you’ll be manually creating a chain of work to load a planet thumbnail in the exoplanet explorer app.

prom2-2

Resources

Instructions
  1. Checkout the first-thumb-start branch and navigate to app/scripts/app.js.
  2. Get the planet data and add the search header.
  3. Create the first thumbnail with createPlanetThumb(data).
  4. Handle errors!
    • Pass ‘unknown’ to the search header.
    • Log the error.

The solution is on the first-thumb-solution branch

git checkout first-thumb-solution

Solution

So here’s how I did it. This first part should look pretty familiar, but there’s one new line right here.

prom2-3

I went ahead and returned the response from the second getJSON(). This getJSON is getting the URL of the first planet.

By returning it, it will get passed to the next .then. When it receives the planetData, it then creates a thumbnail with it.

Before we go on, I want to show you a slightly different syntax. Instead of an anonymous function, I can actually just pass the createPlanetThumb function.

prom2-4

This function will receive the same argument and as the argument that the anonymous function was receiving is the same one that createPlanetThumb was receiving, this works totally fine.

I’ve also got two .catchs.

I’ve got this first one in case there is an error with the search results and I’ve got the second in case anything else went wrong.

Your error handling strategy may have looked slightly different and that’s totally fine.

So the real question is does this code work? It sure does.

prom2-5

Here’s my completed code.

getJSON('../data/earth-like-results.json')
  .then(function (response) {
    addSearchHeader(response.query);
    console.log(response);
    return getJSON(response.results[0]);  // return result for chaining
  }).catch(function () {                  // catch err in search request
    throw Error('Search Request Error');
  }).then(function (planetData) {         // receive result in planetData
    createPlanetThumb(planetData);
    console.log(planetData);
  }).catch(function (error) {             // catch any other error
    addSearchHeader('unknown');
    console.log(error);
  });

Okay, but that’s about as far as we can go just by manually chaining .thens. It’s time to make chains a little bit more interesting.

2.2 Error Handling Strategies

So far, error handling has come in the form of .catches like this one.

get('example.json')
.then(resolveFunc)
.catch(rejectFunc);

But there are actually other ways. These two chunks of code are actually equivalent.

get('example.json')
.then(resolveFunc)
.catch(rejectFunc);
get('example.json')
.then(resolveFunc)
.then(undefined, rejectFunc);

.catch is just shorthand for .then(undefined, ), and then a rejection function.

Notice how, this .then is actually is taking two arguments. The full function signature for .then is actually:

get('example.json').then(resolveFunc, rejectFunc);

In this form, if any previous promise is rejected, the reject function gets called. If they resolve, then the resolve function gets called.

If there is no resolve function and the promise before this .then resolves, then this .then gets skipped over and the next .then is called.

prom2-6

Note: In all cases, as soon as a promise rejects, the JavaScript engine skips to the next reject function in the chain, whether that’s in a .catch or a .then.

So that means an error in the first promise or error in the second promise, both get caught by the following .catch.

prom2-8

Both methods .catch, and .then with two callbacks, work equally well.

prom2-7

However, it’s actually recommended that you use .catch when you can, because .catch is just easier to read and write than a second .then callback which can be hard to spot.

In fact, you might not have even noticed it above right away.

That being said, there is a major difference in the execution order between .catch and a second callback.

Notice that you cannot call both the resolve function and the reject function if they’re part of the same .then.

prom2-9

Only one or the other, or neither will get called.

If something goes wrong with the resolve function, you’ll need another .catch or another .then, farther down the line to catch it.

But if you have .then and then a .catch, each with their own resolve or reject function, both of them can possibly get called.

prom2-10

And finally, I want to make one more subtle point. It’s also worth noting that it isn’t necessarily true, that passing a value to resolve means that the promise succeeded.

prom2-11

If what we pass to resolve is undefined or a promise that rejects, the rejection callback will be called.

This is a subtle point, so go see Jake Archibald’s blog post for more information.

In the next quiz, you’ll be thinking through the flow of a chain of promises using different formats.

Resources

2.3 Quiz: Chained Thenables

There are some subtle differences to error handling strategies that can come back to bite you if you’re not careful.

So for this quiz, I want you to think through a few different scenarios with this long chain.

prom2-12

Now, you normally wouldn’t want to mix and match different syntaxes like I’ve done here, but I want you to think through some different situations.

In this example, async returns a promise, as does recovery. The idea is that the recovery method gets the chain back on track and continuing to resolve if something goes wrong.

My question for you is, for each of these four lines, what numbers will be logged if an error occurs there?

prom2-13

Note that you can assume that no other errors occur.

I’ll go ahead and give you the first one. If an error occurs with async then the first reject function will get called, which logs a 1.

If the recovery works, which you can assume it does, then the next result function will get called, which puts a 3 on the log.

prom2-15

Okay, now it is your turn to finish up the rest.

Solution

For the second problem, if an error occurs here, then the next reject function will get called, which once again, logs out a number is 1.

Then the recovery function happens, and things get back on track. So, the next number is 3. After that, there’s no more log in here so that’s it.

prom2-14

Next problem. If there is an error with this recovery function, now this is an interesting one, because this is only going to get called if there is another error.

So in this case nothing will show up in the log.

For the last one, if something goes wrong with the async function, then the next reject function will get called. In that case, the number 4 should end up on the screen.

So, these can be kind of tricky, but just keep in mind that if something goes wrong, the next reject function will get called.

2.4 Quiz: Series vs Parallel Requests

When you need to perform asynchronous work, the work may not be isolated. You often need to perform multiple asynchronous actions which means you are in the chaining stage of the course.

This is where you will be chaining promises together.

prom2-16

There are two main strategies for performing multiple asynchronous actions.

They are:

Action in series occur one after another, like these three cats all waiting their turn for the rocket.

prom2-17

While actions in parallel all occur simultaneously, like each of these cats getting its own rocket to ride.

You could say that synchronous code is always in series but asynchronous code can either be in series or it can also be in parallel.

Neither option, series or parallel, is inherently better than the other, each has its own purposes.

In the quiz where you fetched the list of planet JSONs and then performed a request for an individual planet JSON, you had to perform the two requests in series because one depended on the other.

prom2-18

But if you need to request a lot of planet JSONs, as you will soon be doing in a quiz, then you will need to programmatically send out the request.

You’ll also want to make the request in parallel because that will reduce the amount of time it takes to load all of the data.

So with that in mind, here’s a quiz for you.

There’s a problem with this code. It appears to be looping over the URLs from the planet search query, but something unexpected will happen.

prom2-19

What is it?

  1. Is it that the requests are being sent in series but they will return in parallel, causing some kind of collision?
  2. Is it that the requests are blocking so that this code will never finish?
  3. Is there simply nothing wrong with this and I just gave you a trick question?
  4. Is it that the thumbnails will be created in a random order?

I want you to pick one of these four answers.

Solution

I’ll start from the first one.

  1. No. The browser is really really good of keeping track of requests. You don’t need to worry about a collision. In fact, I just made that up.
  2. No. Making lots of parallel requests is totally fine for the same reason as I just stated.
  3. No. I don’t like trick questions.
  4. So that must mean that the thumbnails will be created in a random order

Remember, async requests can finish at any time so you cannot predict the order in which requests will return. So you don’t know when any of these getJSON promises will resolve.

This means that they could resolve in a different order than they were created. In that case, the planet thumbnails will be created in a totally random order.

Now, this isn’t necessarily a problem or a bug, but it does lead to the question: Just how do you make the thumbnails appear in the right order?

Keep watching.

2.5 Array Methods & Promises

In the next three quizzes, I’m going to be challenging you to take advantage of array methods, to programmatically create long chains of promises.

prom2-1

Keep in mind that you want the thumbnails to appear on the page in the same order as they appear in the search results, but this does not mean that you need to perform the request in series.

There are different strategies for controlling the order that promises resolve, which you’ll be trying out soon.

The keystone to all of these strategies is the idea of a sequence. You’ll need to create a sequence of promises that are chained, one after another.

2.6 Quiz: .forEach Promises

Does this code look familiar?

prom2-19

For this quiz you’ll be refactoring it and making it actually work. To do so, you’re going to need to create a chain or a sequence of promises.

I want you to loop through the array of URLs that comes in the search results, create a promise foreach one and then use that promise to create a thumbnail once it resolves.

There are probably a few different ways to solve this challenge. You’ll know you’ve done it correctly when the thumbnails show up in the same order on the page as they did in the original URL from the search results.

Also, don’t forget to handle errors. I’m going to let you decide what you want to do with them.

When you’re done I have a question for you.

How are the requests executed? Are they executed in series or in parallel?

prom2-20

Even if you think you know the answer, make sure you check the network panel because you may be surprised.

I’m going to give you some hints to help out. But if you want a challenge, go ahead and skip to the quiz.

I want you to think about a loop that looks like this.

prom2-21

For every iteration through the loop, x increases by 1. Before the iteration, x is at 0. Then you add 1 to x and get 1. You add another 1 to x and get 2. Another one, you get 3.

At some point you’ve chained so many ones together that you’ve got 10.

prom2-22

I want you to treat the sequence similarly, but don’t use addition here.

For every iteration through the loop, add another .then to the end of the sequence.

prom2-23

Remember that for each planet you’re going to need two actions.

  1. You’ll need to get the JSON
  2. You’ll need to create a planet thumbnail.

In this way the sequence will grow by one request and one thumbnail for every iteration.

Resources

Instructions
  1. Checkout the foreach-start branch and navigate to app/scripts/app.js.
  2. Refactor .forEach to create a sequence of Promises that always resolves in the same order it was created.
    • Fetch each planet’s JSON from the array of URLs in the search results.
    • Call createPlanetThumb(data) on each planet’s response data to add it to the page.
  3. Use developer tools to determine if the planets are being fetched in series or in parallel.

The solution is on the foreach-solution branch.

Solution

Okay so there are different answers to this question depending on how you solve the problem.

prom2-20

Let me show you my solution first, and then I’ll talk through how you may have come to a different conclusion.

We first start with Promise.resolve(). We then loop through the urls adding a set of actions (with thens) to the sequence.

prom2-24a

I’m iterating through the URLs and adding two .thens for each URL. The first gets the JSON data for the planet and then the second creates the planet thumbnail.

prom2-24

As forEach iterates through the array of planet data urls, the sequence gets longer by two .thens each time.

Each .then will need to wait for the promise before it to resolve before it can execute.

There’s some good news and some bad news.The good news is that the planet thumbnail show up in the right order. The bad news is that it’s happening in series.

You can pretty clearly see that each request depends on the one before it finishing.

prom2-25

In order to execute the requests in parallel, your code needs to look something like the following code.

prom2-26

Notice that I’m not actually adding to the sequence, rather, I’m simply adding two .thens which quickly get overwritten by the next iteration through the loop.

Luckily, these two .thens stay attached to one another, and so they continue to execute.

If your code looked like this, then you probably saw the request come in like so.

prom2-27

They’re in parallel and the problem is that there’s no guarantee about the order, and to be honest, for many applications this is completely fine. You can handle ordering in other ways with your front end.

But if the order that promises resolve is important to your app, this kind of code could easily become a subtle source of bugs later.

Browsers can request many resources simultaneously so it makes a lot of sense to run code like this in parallel. But if you want to do so, you probably should where it’s really, really obvious that your requests are happening in parallel.

In fact, you’ll be doing that in the next quiz.

2.7 Quiz: .map Promises

.map is an array method that accepts a function, represented by the box and a rocket ship, and it returns an array.

prom2-28

The array it returns is going to be the result of executing this function against every element in this array.

Here, let me show you what I mean. It starts with the first element. It creates the array and passes this element into the function and executes it immediately.

prom2-29

So at this point, this function is running. Then .map moves to the next element.

prom2-30

Now it’s in the array and executing as well.

It goes to the next, and so on until it’s created this new array and for each element in the array, the function has already executed.

prom2-31

In this case, the array you want to iterate against is the URLs, and you’ll want to call getJSON and createPlanetThumb against each one.

prom2-32

Okay, and I lied about sequences earlier. You don’t actually need to create a sequence in order to use .map, however there is a bonus challenge at the very end of this course.

Your solution to this quiz is going to be the starting point for it.

prom2-33

You’ll probably find that you’ll need a sequence to solve it.

Anyway, you need to map the URLs against the methods you need to load the thumbnails.

When you’re done you should see the thumbnails appear on the screen.

prom2-34

You should also see the requests go out in parallel.

prom2-27

Resources

Instructions
  1. Checkout the map-start branch and navigate to app/scripts/app.js.
  2. Use .map to fetch all the planets in parallel.
    • Call .map on an array and pass it a function.
    • .map will execute the function against each element in the array immediately.

You can find the solution by checking out the map-solution branch.

Solution

prom2-35

So this is how I did it. I’m mapping an anonymous function against every URL in the results array.

prom2-36

The function takes the URL and then gets the planet’s data. Once it has that it creates the planet thumbnail.

All right.Well, that’s pretty simple and there was no need for a sequence here. But remember, there is no guarantee to the order when you’re using .map.

If order matters, you’ll need some additional logic in your app to handle it.

All right, it’s time for the final promise method. Let’s go to last quiz.

2.8 Quiz: All Promises

Here is one last promise method that you should learn to use. It’s .all.

prom2-37

.all takes an array of promises, executes them, and then it returns an array of values in the same order as the original promises.

prom2-38

.all fails fast, in that it will reject as soon as the first promise rejects, without waiting for the rest of the promises to settle.

prom2-39

This means that even if one of the promises rejects, the whole .all rejects. But once every promise has resolved, the next .then in the chain gets the array of values.

For this quiz, I want you to refactor this code from the previous quiz, using .all.

When you do it correctly, of course you should see the thumbnails show up

prom2-34

and you should also see parallel network requests.

prom2-27

Resources

Instructions
  1. Checkout the all-start branch and navigate to app/scripts/app.js.
  2. Use Promise.all() to refactor the .map code by passing Promise.all() an array of Promises.
    • Each Promise will be executed in parallel.
    • The return values will be returned in the same order as the Promises were created.

Hint: you’ll probably still need to use .map.

Solution

prom2-40

So this is how I did it.

.map returns an array so I create an arrayOfPromises by passing all of the URLs to getJSON.

prom2-41

Now remember at this point, because they’ve been mapped, this function immediately runs against every URL.

So in reality, I have an arrayOfPromises that are basically already executing, and then I simply pass them into .all.

Once all of the promises settle, then the next link in the chain runs.

In this case the .then receives an arrayOfPlanetData, which is in the same order as the arrayOfPromises and then for every planet, I create a planet thumbnail.

Incidentally, I could make this section a little bit more terse. Let me show you how.

First off, I don’t need this anonymous function.

prom2-41

This is because the getJSON function will receive the same arguments as the anonymous function.

prom2-42

And of course I don’t actually need to create a variable. I can simply pass the array created by .map straight to .all.

prom2-43

Anyway, let’s see how this looks.

prom2-34

All right, the planets are once again there in the correct order, and I’m seeing all of the requests happen in parallel.

prom2-27

The cool thing is because .all guarantees the order of the resulting array, I know that all the thumbnails will end up on the page in the correct order.

There is one more performance optimization that’s possible with this code. Some people may or may not think it’s a good idea, but it’s definitely a good challenge.

So after this course is over, at the very end of it, there’s a bonus challenge for you to optimize this code even further.

My Solution

Here’s the code I created as a solution for this challenge.

getJSON('../data/earth-like-results.json').then(function (response) {
  addSearchHeader(response.query);
  return Promise.all(response.results.map(function (url) {
    return getJSON(url);
  })).then(function (planetData) {
    planetData.map(function (planet) {
      createPlanetThumb(planet);
    });
  }).catch(function (err) {
    console.log('planet error:', err);
  });
}).catch(function (err) {
  console.log('earth-like-results:', err);
});

Here’s the condensed version.

getJSON('../data/earth-like-results.json').then(function (response) {
  addSearchHeader(response.query);
  return Promise.all(response.results.map(getJSON)).then(function (planetData) {
    planetData.map(createPlanetThumb);
  }).catch(function (err) {
    console.log('planet error:', err);
  });
}).catch(function (err) {
  console.log('earth-like-results:', err);
});

2.9 Wrap up

There are hundreds of billions of planets in the Milky Way, but they’re really hard to spot.

Humanity’s search for other earths has only yielded a small fraction of the possibilities.

prom1-2

If you’re interested in learning about exoplanets and the techniques astronomers use to spot them, keep watching after this video.

I hoped you enjoyed learning a little astronomy alongside JavaScript. Of course you came here to learn about Promises, the easiest technique for handling asynchronous work with JavaScript.

Here’s what we covered.

It’s been a lot of fun. I want you to go out there and use Promises to simplify your life, and don’t forget to look up every once in a while.

2.10 Exoplanets 101

What is an exoplanet

Exoplanets are planets that exist outside of our Solar System. Most exoplanets orbit other stars. In some cases, they’ll orbit two (called a binary) or three stars, and in rare cases they’ll float freely through the galaxy without a home star!

Exoplanets take on a huge range of sizes, temperatures, compositions and distances from stars.

It’s interesting to wonder if life could exist on different worlds, but to be honest, it’s really hard to know with the little bit of data that we get from lightyears away.

The key to finding life, we assume, is finding liquid water. Astronomers get excited about planets in the habitable zones around stars, however it’s still possible for life to form closer to or farther from their home stars.

For instance, Jupiter’s moon Europa and Saturn’s moon Enceladus both hold huge oceans of liquid water. Yet, both moons are well outside the Sun’s habitable zone. Instead of capturing heat from sunlight, internal geological processes and insulating crusts of ice keep them warm enough for liquid water.

Most exoplanets that have been found so far are huge, which makes sense. The bigger the planet, the easier it is to spot. But astronomers are getting better at spotting smaller and smaller planets. The record now for smallest exoplanet spotted so far is Kepler-37b.

How are exoplanets found

The Transit Method

Astronomers who use the transit method measure the brightness of a star over time. They look for repeated, predictable dips in a star’s brightness. The duration and intensity of the dip reveal the radius of the planet.

Radial Velocity and Astrometry Methods

Astronomers who use radial velocity and astrometry measure the position of a star over time. Just as stars tug on planets, planets tug on stars. If a planet is much smaller or very far away from a star, the tug is difficult to measure. But if a planet is very close or very large, it’s tug will make the star wobble back and forth.

Watch this video and pay attention to the atheletes’ spinning movement just before they release the hammer. See how it makes them spin around? That’s exactly the kind of movement astronomer’s try to spot.

The difference between the methods is the difference in the way the motion appears to us. For radial velocity, we’re seeing the motion happen from the same plane so it looks side-to-side. For astrometry, we’re looking down (or up?) perpendicularly to the planet’s orbit so the motion looks circular.

Timing Methods

Astronomers who use timing methods watch for changes in periodic events. There are a few different periodic events that astronomers have used to find planets.

Pulsars are rapidly rotating stellar cores left behind after supernovas. They emit periodic bursts of radio waves as they rotate that are incredibly regular. Nearby planets will tug on pulsars, minutely changing their periodicity. The first exoplanet,PSR B1257+12, was found this way.

Astronomers also use a combination of the transit method with timing methods to determine if there are multiple planets orbiting a star.

Gravitational Microlensing

Astronomers who use gravitational microlensing take advantage of general relativity. Light, like normal matter, is affected by gravity. Big objects, like planets, stars and galaxies will actually bend light that passes by. By analyzing the light coming from stars or galaxies behind a thing and looking for distortions, astronomers can measure the mass of the thing, which in some cases, is a planet.

Direct Imaging

Astronomers who use direct imaging visually spot planets with telescopes. Planets are very, very, very dim compared to stars however, so astronomers have to block out the light of a planet’s home star to be able to see it.

2.11 Bonus Quiz: Parallel Requests

This quiz was going to be the last quiz in the course. It’s a bit crazy. And it’s difficult. And in all reality you’ll probably never need to (or want to!) write anything like this in real life.

But! As I was discussing it with Art, our Director of Engineering, he asked me where he could find the code so that he could turn this challenge into an interview question. So I figured, “why not?”

Here it is as a bonus question.

Requirements

I want you to write code that is capable of:

Feel free to change any or all of the helper methods (like createPlanetThumb)!

As an example, here’s an array of Promises:

getJSON(url1).then(createPlanetThumb)
.then(function(){
  return getJSON(url2).then(createPlanetThumb);
}
.then(function(){
  return getJSON(url3).then(createPlanetThumb);
}
.then(function(){
  return getJSON(url4).then(createPlanetThumb);
}
.then(function(){
  return getJSON(url5).then(createPlanetThumb);
}

All of the getJSONs should execute in parallel. But, if url3 arrives first, it should wait for url1 to resolve and url2 to resolve before creating its thumbnail. The state of url4 and url5 don’t affect url3.

In other words, each .then executes as soon as all of the .thens before it resolve, yet all of the network requests are executed in parallel.

Words of Encouragement and Acknowledgement

This is a combination of the last few array method quizzes. It’s tough but you’ve already used all of the methods and techniques you’ll need to conquer this quiz. You can do it!

I recommend using network throttling too. Use 3G and you’ll see some jitter with respect to the order in which requests finish.

Also, I can’t say I came up with this quiz on my own. This quiz, and much of this course, was inspired by this guide to Promises by Jake Archibald.

He also reviewed this course and helped me with a few inaccuracies with the first draft of the script. If you scan through the article, you’ll find the general strategy for solving this quiz. Don’t look if you want a challenge!

Checkout the bonus-start branch to get started! Good luck!

The solutions can be found by checking out the bonus-solution branch.

Solution

prom2-44

Here’s my solution! There are a few ways of doing this and I wanted to keep my logic as obvious as possible. I made changes to createPlanetThumb (it’s now a Promise) and getJSON (for testing purposes) as well. And I added some logging to get a clearer picture of the rendering process.

Here are the relevant changes (you can always checkout bonus-solution to see it too). Underneath, you’ll find a discussion of my overall strategy.

In this method we wrap the action in a Promise which we return. We also make sure to resolve the promise once the action completes.

/**
 * Helper function to create a planet thumbnail - Promisified version!
 * @param  {Object} data - The raw data describing the planet.
 */
function createPlanetThumb(data) {
  return new Promise(function(resolve) {
    var pT = document.createElement('planet-thumb');
    for (var d in data) {
      pT[d] = data[d];
    }
    home.appendChild(pT);
    console.log('rendered: ' + data.pl_name);
    resolve();
  });
}

/**
 * Performs an XHR for a JSON and returns a parsed JSON response - with a delay!
 * @param  {String} url - The JSON URL to fetch.
 * @return {Promise}    - A promise that passes the parsed JSON response.
 */
function getJSON(url) {
  console.log('sent: ' + url);
  return get(url).then(function(response) {
    // For testing purposes, I'm making sure that the urls don't return in order
    if (url === 'data/planets/Kepler-62f.json') {
      return new Promise(function(resolve) {
        setTimeout(function() {
          console.log('received: ' + url);
          resolve(response.json());
        }, 500);
      });
    } else {
      console.log('received: ' + url);
      return response.json();
    }
  });
}

window.addEventListener('WebComponentsReady', function() {
  home = document.querySelector('section[data-route="home"]');

  getJSON('../data/earth-like-results.json')
  .then(function(response) {
    addSearchHeader(response.query);
    return response;
  })
  .then(function(response) {
    var sequence = Promise.resolve();

    // .map executes all of the network requests immediately.
    var arrayOfExecutingPromises = response.results.map(function(result) {
      return getJSON(result);
    });

    arrayOfExecutingPromises.forEach(function(request) {
      // Loop through the pending requests that were returned by .map (and are in order) and
      // turn them into a sequence.
      // request is a getJSON() that's currently executing.
      sequence = sequence.then(function() {
        // Remember that createPlanetThumb is a Promise, so it must resolve before Promises
        // later in the sequence can execute.
        return request.then(createPlanetThumb);
      });
    });
  });
});

Here’s how we know it’s working:

prom2-45

This is a three step process:

  1. Create an array of network requests that are executing in parallel.
  2. Attach a Promisified version of createPlanetThumb to each request in order to render thumbnails.
  3. Create a sequence of Promises, each of which is a request and thumbnail rendering. I imagine it like this (each ‘–>’ is a .then):

[ (request –> render) –> (request –> render) –> (request –> render) –> … ]

The requests can resolve whenever, but the renders will block the sequence because they’re now Promises that resolve only after the thumbnail has been created. Each render must wait for all of the renders before it to resolve before executing.

.map takes care of the parallel requests part and .forEach chains the requests into a sequence.

There are other ways of writing this more succinctly, perhaps using .reduce. Either way, this code works and I’m happy. How did your solution compare to mine?

My Solution

createPlanetThumb is wrapped in a promise which we return when the action completes. This method also console.logs when the output is rendered.

function createPlanetThumb(data) {
  return new Promise(function (resolve) {
    var pT = document.createElement('planet-thumb');
    for (var d in data) {
      pT[d] = data[d];
    }
    home.appendChild(pT);

    console.log('rendered:', data.pl_name);
    resolve();
  });
}

getJSON gets the contents of a json file but has some additional testing code to delay one of the urls so they don’t return in order. We also log the order the request is sent and the order it is received.

function getJSON(url) {
  console.log('sent:', url);
  return get(url).then(function(response) {
    // return response.json();

    // For testing purposes, making sure that the urls don't return in order
    if (url === 'data/planets/Kepler-62f.json') {
      return new Promise(function(resolve) {
        setTimeout(function() {
          console.log('received: ' + url);
          resolve(response.json());
        }, 500);
      });
    } else {
      console.log('received: ' + url);
      return response.json();
    }
  });
}

This method gets the initial json file and then maps through the array of results calling and rendering each.

window.addEventListener('WebComponentsReady', function() {
  home = document.querySelector('section[data-route="home"]');
  getJSON('../data/earth-like-results.json').then(function (response) {
    console.log(response);
    addSearchHeader(response.query);
    return Promise.all(response.results.map(function (url) {
      // console.log('sent:', url);
      return getJSON(url);
    })).then(function (planetData) {
      return planetData.map(function (planet) {
        return createPlanetThumb(planet);
      });
    });
  }).catch(function (e) {
    console.log('earth-like-results:', e);
  });
});

prom2-46