Promise resources

Part of “Learn to write async code in JavaScript.”

Table of contents

MDN guides

If you want to learn about promises, I suggest you start by reading the MDN guide: “Using Promises”, and after that the Promise reference or “Creating a promise”. They have a simple language, especially the first, and a lot of examples—I find the last important if you want to learn a new code concept.

Notes from MDN - Using Promises

“MDN - Using Promises”

  • Promises always run after the current task in the event loop. That means that they can’t run or even complete while the current task still executes—something that’s possible with callbacks. The callback bit is from YDKJS, and I think that he means 3rd party callbacks, like a payment API.
  • You can create chains of promises by adding multiple thens after a promise, transforming the result in each step. Chaining.
  • Each then returns a promise (or undefined if you forget to return something) even if it doesn’t look like a promise—if it’s a string or a number, for example. Chaining.
  • catch is a shorthand for then(null, errorHandler).
  • You can add a listener for unhandled rejected promises in the browser (in Node they are logged automatically in the console). Promise rejection events.
  • The promise chain stops if there is an exception, and control is transferred to the next error handler. Error propagation.
  • Some common mistakes are: not returning something inside then, unnecessary nesting, and not registering a catch method at the end. Common Mistakes.

Jake Archibald on promises

A lot of information from the previous MDN articles is also available in Jake Archibald’s article on promises. He starts by introducing you to promises, but the difficulty rises quickly with some advanced usage tips.

Notes from Jake Archibald’s article on promises

JavaScript Promises: an Introduction

  • It’s customary to pass an error object to reject because it can capture the stack trace. There is also an ESLint rule you can enable for that.

  • then takes two functions as parameters: one for success, and one for failure. Both are optional.

  • If you return a simple value inside a then, the next then gets that value as input—and resolves immediately. Use it to transform values. But if you return a promise, the code waits for that promise. Queueing asynchronous actions.

  • Prefer catch over then(undefined, function) or just then(success, failure) because it’s more readable. Error handling.

  • A subtle difference between then(func1, func2) and then(func1).catch(func2) is that in the first case only func1 will be called if it rejects. In the second case, both will be called if func1 rejects. Error handling.

  • “Rejections happen when a promise is explicitly rejected, but also implicitly if an error is thrown in the constructor callback.” They also get rejected if an error is thrown inside a then or even inside a catch. JavaScript exceptions and promises.

  • Use Promise.resolve() to resolve (success or failure) a promise and Promise.reject() to create a rejecting promise.

  • He shows a trick on how to make a forEach loop async aware:

    // Start with a promise that always resolves
    var sequence = Promise.resolve();
    
    // Loop through our chapter urls
    story.chapterUrls.forEach(function(chapterUrl) {
      // Add these actions to the end of the sequence
      sequence = sequence
        .then(function() {
          return getJSON(chapterUrl);
        })
        .then(function(chapter) {
          addHtmlToPage(chapter.html);
        });
    });

    I prefer Promise.all(array of promises) for parallel or a for-of loop and await for sequential processing because the code above looks weird. Having said that, he later improves the readability by using a reduce instead of a simple variable. Creating a sequence.

    // Loop through our chapter urls
    story.chapterUrls.reduce(function(sequence, chapterUrl) {
      // Add these actions to the end of the sequence
      return sequence
        .then(function() {
          return getJSON(chapterUrl);
        })
        .then(function(chapter) {
          addHtmlToPage(chapter.html);
        });
    }, Promise.resolve());
  • Keep in mind that those promises above don’t run in parallel. He later shows a solution with Promise.all to run all the promises at once.

  • But the best trick is to start them in parallel, and at the same time, process each one after it completes with map + reduce:

    getJSON("story.json")
      .then(function(story) {
        addHtmlToPage(story.heading);
    
        // Map our array of chapter urls to
        // an array of chapter json promises.
        // This makes sure they all download in parallel.
        return story.chapterUrls
          .map(getJSON)
          .reduce(function(sequence, chapterPromise) {
            // Use reduce to chain the promises together,
            // adding content to the page for each chapter
            return sequence
              .then(function() {
                // Wait for everything in the sequence so far,
                // then wait for this chapter to arrive.
                return chapterPromise;
              })
              .then(function(chapter) {
                addHtmlToPage(chapter.html);
              });
          }, Promise.resolve());
      })
      .then(function() {
        addTextToPage("All done");
      })
      .catch(function(err) {
        // catch any error that happened along the way
        addTextToPage("Argh, broken: " + err.message);
      })
      .then(function() {
        document.querySelector(".spinner").style.display = "none";
      });

YDKJS - Async & Performance - Chapter 3 Promises

I like Kyle Simpson’s YDKJS book series; I’ve learned a lot from them. The main problem with this book for me, as a non-native English speaker, is the use of complicated language without good reason. The topic is complex enough for a beginner; there’s no need to further complicate things. It also showed me why it’s a bad idea to use phrases like: “As you may have guessed.” The points he makes are valuable though. I suggest reading it after you read the previous resources.

Notes from YDKJS

  • What MDN calls an executor function here is called a revealing constructor pattern. Promise “events.”
  • Once a promise is resolved, its value is immutable. You can observe it as many times as you like—that doesn’t mean you should though. Promise value.
  • A lot of hate for inversion of control; Java developers love it by the way.
  • The code inside the executor runs immediately and synchronously.
  • Gives a tip to never rely on the promise ordering. Calling too late.
  • If you try to pass more than one parameter to resolve or reject, the rest parameters will be ignored.
  • If we are not sure if a thenable object is a promise or not, we can pass it inside a Promise.resolve(promise). What we’ll get will be a trusted promise. Trustable promise?
  • Promise.resolve() is a good name because it can either return a successful (fulfilled) or an unsuccessful (rejected) promise. Promise.reject() always returns a rejected promise. The same is true for the first argument of the executor function. The point is that resolve has a double nature. Terminology: Resolve, Fulfill, and Reject.
  • A use case for Promise.race() is to use it as a timeout for a promise. Timeout race. Be careful, though, it has some gotchas outlined in the previous link and the following (in short): Promise.all and Promise.race.
  • Concurrent iterations with promises where he creates a Promise.map method. Concurrent iterations.
  • A recap on promise API; it’s not bad: Promise API recap.
  • Promise limitations: Sequence error handling, single value, single resolution, inertia, non-cancelable, and performance.
  • A Promise.wrap utility to wrap a promise over a function (Promise factory). Inertia.

Other things to read

Popular posts

Other notes