JavaScript promises: 3 gotchas and tricks to avoid them

Promises, handlers, and chains are hardly straightforward. Follow these tips to keep your code clean and simple

JavaScript promises: 3 gotchas and tricks to avoid them
Thinkstock

Now that we’ve covered some of the basic concepts and implementation of promises, let’s take a look at three promises gotchas that I’ve seen trip up developers as well as some tricks that might be useful.

Gotcha #1: Promise handlers return promises

If you’re returning information from a then or catch handler, it will always be wrapped in a promise, if it isn’t a promise already. You never need to write code like this:

firstAjaxCall.then(() => {
  return new Promise((resolve, reject) => {
    nextAjaxCall().then(() => resolve());
  });
});

In the case above, since nextAjaxCall also returns a promise, you can just do this:

firstAjaxCall.then(() => {
  return nextAjaxCall();
});

Additionally, if you’re returning a plain (non-promise) value, the handler will return a promise resolved to that value, so you can continue to call then on the results:

firstAjaxCall.then((response) => {
  return response.importantField
}).then((resolvedValue) => {
  // resolvedValue is the value of response.importantField returned above
  console.log(resolvedValue);
});

Trick #1: Use Promise.resolve()

If you want to operate on an incoming value that may or may not be a promise already, simply use the static method Promise.resolve(). For example, if you get a variable that may or may not be a promise, simply pass it as an argument to Promise.resolve. If the variable is a promise, the method will return the promise. If the variable is a value, the method will return a promise resolved to the value.

let processInput = (maybePromise) => {
  let definitelyPromise = Promise.resolve(maybePromise);
  definitelyPromise.then(doSomeWork);
};

Gotcha #2: .then always takes a function

I can’t tell you how many times I've seen (and definitely written) promise code that looks something like this:

let getAllArticles = () => {
  return someAjax.get('/articles');
};
let getArticleById = (id) => {
  return someAjax.get(`/articles/${id}`);
};

getAllArticles().then(getArticleById(2));

The intent of the above code is to get all of the articles first, then when that’s done, get the article with the ID of 2. In fact, although we may have wanted a sequential execution, these two promises are essentially being kicked off at the same time. They could complete in any order.

If I had to guess, I’d say this confusion arises from the natural language reading of promises, which causes people to forget some of the more fundamental rules of JavaScript. The issue is that arguments to functions are always evaluated before being passed into the function. then is not receiving a function; it’s receiving the return value of getArticleById because we’re calling getArticleById immediately with the parentheses operator.

If you want sequential processing of these two functions, you could do something like this:

// A little arrow function is all you need
getAllArticles().then(() => getArticleById(2));

By wrapping the call to getArticleById in an arrow function, we provide .then with a function it can call when getAllArticles() has resolved.

Trick #2: Pass in named functions to .then

You don’t always have to use inline anonymous functions as arguments to .then. You can easily assign a function to a variable and pass the reference to that function to .then instead. Building on our previous example:

/* function definitions from Gotcha #2 */
let getArticle2 = () => {
  return getArticleById(2);
};

getAllArticles().then(getArticle2);

Notice I’m passing in the reference to the function and not calling it, which was our issue in Gotcha #2.

Gotcha #3: Non-functional .then arguments

Let’s take our example from Gotcha #2 and add a little extra processing to the end of the chain:

let getAllArticles = () => {
  return someAjax.get('/articles');
};
let getArticleById = (id) => {
  return someAjax.get(`/articles/${id}`);
};

getAllArticles().then(getArticleById(2)).then((article2) => { /* Do something with article2 */});

We already know that this chain won’t run sequentially as we want it to, but now we’ve uncovered some quirky behavior in promise land. What do you think the value of article2 is in the last .then?

Since we’re not passing a function into the first argument of .then, JavaScript (i.e. the promise specification) passes in the initial promise with its resolved value, so the value of article2 is whatever getAllArticles() has resolved to. If you have a long chain of .then methods and some of your handlers are getting values from earlier .then methods, make sure you’re actually passing in functions to .then.

Trick #3: Pass in named functions with formal parameters

Building on our Trick #2, we can pass in named functions that define a single formal parameter (i.e., take one argument). This allows us to create some generic functions that we can use within a chain of .then methods or outside the chain in a clean way.

Let’s say we have a function, getFirstArticle, that makes an API call to get the newest article in a set and resolves to an article object with properties like ID, title, and publication date. Then say we have another function, getCommentsForArticleId, that takes an article ID and makes an API call to get all of the comments associated with that article. Now all we need to connect the two functions is to get from the resolution value of the first function (an article object) to the expected argument value of the second function (an article ID). We could do that using an anonymous inline function:

getFirstArticle().then((article) => {
  return getCommentsForArticleId(article.id);
});

Or we could create a simple function that takes an article, returns the ID, and chains everything together with .then:

let extractId = (article) => article.id;
getFirstArticle().then(extractId).then(getCommentsForArticleId);

This may look like it obscures the resolution value of each function since they’re not defined inline, but it creates some flexible functions that could likely be reused. Notice also that we’re using what we learned in Gotcha #1: Although extractId doesn’t return a promise, .then will wrap its return value in a promise, which lets us call .then again!

Got any other tripping points or promise tricks you want to share? Leave a comment or start the conversation with me on Twitter.

Copyright © 2018 IDG Communications, Inc.