Promise resources
Table of contents
Part of “Learn to write async code in JavaScript.”
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
- 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
then
s 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 forthen(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 nextthen
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
overthen(undefined, function)
or justthen(success, failure)
because it’s more readable. Error handling.A subtle difference between
then(func1, func2)
andthen(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 acatch
. JavaScript exceptions and promises.Use
Promise.resolve()
to resolve (success or failure) a promise andPromise.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 andawait
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
- Reveal animations on scroll with react-spring
- Gatsby background image example
- Extremely fast loading with Gatsby and self-hosted fonts