loading...
Cover image for 3 most common mistakes when using Promises in JavaScript

3 most common mistakes when using Promises in JavaScript

mpodlasin profile image Mateusz Podlasin Originally published at Medium ・8 min read

Promises rule JavaScript. Even nowadays, with introduction of async/await, they are still an obligatory knowledge for any JS developer.

But JavaScript differs in how it deals with asynchronicity from other programming languages. Because of that, even developers with a lots of experience can sometimes fall into its traps. I have personally seen great Python or Java programmers making very silly mistakes when coding for Node.js or browsers.

Promises in JavaScript have many subtleties which one has to be aware of in order to avoid those mistakes. Some of them will be purely stylistic, but many can introduce actual, difficult to track errors. Because of that, I have decided to compile a short list of the three most common mistakes I have seen developers do, when programming with Promises.

Wrapping everything in a Promise constructor

This first mistake is one of the most obvious, and yet I have seen developers do it surprisingly often.

When you first learn about Promises, you read about a Promise constructor, which can be used to create new Promises.

Perhaps because people often start learning by wrapping some browser APIs (like setTimeout) in the Promise constructor, it gets ingrained in their minds that the only way to create a Promise is to use the constructor.

So as a result they often end up with a code like this:

const createdPromise = new Promise(resolve => {
  somePreviousPromise.then(result => {
    // do something with the result
    resolve(result);
  });
});

You can see that in order to do something with the result from somePreviousPromise someone used then, but later decided to wrap it again in a Promise constructor, in order to store that computation in the createdPromise variable, presumably in order to do some more manipulations on that Promise later.

This is of course unnecessary. The whole point of then method is that it itself returns a Promise, that represents executing somePreviousPromise and then executing a callback passed to the then as an argument, after somePreviousPromise gets resolved with a value.

So the previous snippet is roughly equivalent to:

const createdPromise = somePreviousPromise.then(result => {
  // do something with result
  return result;
});

Much nicer, isn’t it?

But why I wrote that it is only roughly equivalent? Where is the difference?

It might be hard to spot for the untrained eye, but in fact there is a massive difference in terms of error handling, much more important than the ugly verbosity of the first snippet.

Let’s say that somePreviousPromise fails for any reason and throws an error. Perhaps that Promise was making a HTTP request underneath and an API responded with a 500 error.

It turns out that in the previous snippet, where we wrap a Promise into another Promise, we have no way to catch that error at all. In order to fix that, we would have to introduce following changes:

const createdPromise = new Promise((resolve, reject) => {
  somePreviousPromise.then(result => {
    // do something with the result
    resolve(result);
  }, reject);
});

We simply added a reject argument to the callback function and then used it by passing it as a second parameter to the then method. It’s very important to remember that then method accepts second, optional parameter for error handling.

Now if somePreviousPromise fails for any reason, reject function will get called and we will be able to handle the error on createdPromise as we would do normally.

So does this solve all of the problems? Unfortunately no.

We handled the errors that can occur in the somePreviousPromise itself, but we still don’t control what happens within the function passed to the then method as a first argument. The code that gets executed in the place where we have put the // do something with the result comment might have some errors. If the code in this place throws any kind of error, it will not be caught by the reject function placed as a second parameter of the then method.

That’s because error handling function passed as a second argument to then only reacts to errors that happen earlier in our method chain.

Therefore, the proper (and final) fix will look like this:

const createdPromise = new Promise((resolve, reject) => {
  somePreviousPromise.then(result => {
    // do something with the result
    resolve(result);
  }).catch(reject);
});

Note that this time we used catch method, which — because it gets called after the first then — will catch any errors that get thrown in the chain above it. So whether the somePreviousPromise or the callback in then will fail — our Promise will handle it as intended in both of those cases.

As you can see, there are many subtleties when wrapping code in Promise constructor. That’s why it’s better to just use then method to create new Promises, as we have shown in a second snippet. Not only it will look nicer, but we will also avoid those corner cases.

Consecutive thens vs parallel thens

Because many programmers have Object Oriented Programming backgrounds, it’s natural for them that a method mutates an object rather than creates a new one.

It’s probably why I see people being confused about what exactly happens when you call a then method on a Promise.

Compare those two code snippets:

const somePromise = createSomePromise();

somePromise
  .then(doFirstThingWithResult)
  .then(doSecondThingWithResult);
const somePromise = createSomePromise();

somePromise
  .then(doFirstThingWithResult);

somePromise
  .then(doSecondThingWithResult);

Do they do the same thing? It might seem so. After all, both code snippets involve calling then twice on somePromise, right?

No. It’s a very common misconception. In fact, those two code snippets have a completely different behavior. Not fully understanding what is happening in both of them can lead to tricky mistakes.

As we wrote in a previous section, then method creates a completely new, independent Promise. This means that in the first snippet, second then method is not being called on somePromise, but on a new Promise object, that encapsulates (or represents) waiting for somePromise to get resolved and then calling doFirstThingWithResult right after. And then we add a doSecondThingWithResult callback to this new Promise instance.

In effect, the two callbacks will be executed one after another — we have a guarantee that the second callback will be called only after the first callback finishes execution without any issues. What is more, the first callback will get as an argument a value returned by somePromise, but the second callback will get as an argument whatever is returned from the doFirstThingWithResult function.

On the other hand, in the second code snipped, we call then method on somePromise twice and basically ignore two new Promises that get returned from that method. Because then was called twice on exactly the same instance of a Promise, we don’t get any guarantees about which callback will get executed first. The order of execution here is undefined.

I sometimes think about it as “parallel” execution, in a sense that the two callbacks should be independent and not rely on any of them being called earlier. But of course in reality JS engines execute only one function at a time — you simply don’t know in which order they will be called.

The second difference is that both doFirstThingWithResult and doSecondThingWithResult in the second snippet will receive the same argument — the value that somePromise gets resolved to. Values returned by both the callbacks are completely ignored in that example.

Executing a Promise immediately after creation

This misconception also comes from the fact that most coders are often experienced in Object Oriented Programming.

In that paradigm, it is often considered a good practice to make sure that an object constructor does not perform any actions by itself. For example an object representing a Database should not initiate the connection with the database when its constructor is called with the new keyword.

Instead, it’s better to provide special method — for example called init — that will explicitly create a connection. This way an object does not perform any unintended actions only because it was initiated. It patiently waits for a programmer to explicitly ask for executing an action.

But that’s not how Promises work.

Consider the example:

const somePromise = new Promise(resolve => {
  // make HTTP request
  resolve(result);
});

You might think that the function making an HTTP request does not get called here, because it is wrapped in a Promise constructor. In fact, many programmers expect that it gets called only after a then method gets executed on a somePromise.

But that’s not true. The callback gets executed immediately when that Promise is created. It means that when you are in the next line after creating somePromise variable, your HTTP request is probably already being executed, or at least scheduled.

We say that a Promise is “eager” because it executes an action associated with it as fast as possible. In contrast, many people expect the Promises to be “lazy” — that is to perform an action only when it is absolutely necessary (for example when a then gets called for the first time on a Promise). It’s a misconception. Promises are always eager and never lazy.

But what you should do if you want to execute the Promise later? What if you want hold off with making that HTTP request? Is there some magic mechanism built into the Promises that would allow you to do something like that?

The answer is more obvious than the developers sometimes would expect. Functions are a lazy mechanism. They are executed only when programmer explicitly calls them with a () bracket syntax. Simply defining a function doesn’t really do anything just yet. So the best way to make a Promise lazy is… to simply wrap it in a function!

Take a look:

const createSomePromise = () => new Promise(resolve => {
  // make HTTP request
  resolve(result);
});

Now we wrapped the same Promise constructor call in a function. Because of that nothing really gets called yet. We also changed a variable name from somePromise to createSomePromise, because it is not really a Promise anymore — it is a function creating and returning a Promise.

The Promise constructor — and hence the callback function with a HTTP request — will only be called when we execute that function. So now we have a lazy Promise, that gets executed only when we really want it.

What is more, note that for free we got another capability. We can easily create another Promise, that performs the same action.

If for some weird reason we would like to make the same HTTP call twice and execute those calls concurrently, we can just call the createSomePromise function twice, one immediately after another. Or if a request fails for any reason, we can retry it, using the very same function.

This shows that it’s extremely handy to wrap Promises in functions (or methods) and hence it is a pattern that should become natural for a JavaScript developer.

Ironically, if you have read my article on Promises vs Observables, you know that programmers being introduced to Rx.js often make an opposite mistake. They code Observables as if they are eager (like Promises), while in fact they are lazy. So, for example, wrapping Observables in a function or a method often does not make any sense and in fact can even be harmful.

Conclusion

I have shown you three types of mistakes that I have often seen being made by developers who knew Promises in JavaScript only superficially.

Are there any interesting types of mistakes that you have encountered either in your code or in the code of others? If so, share them in the comment.

If you enjoyed this article, considered following me on Twitter, where I will be posting more articles on JavaScript programming.

Thanks for reading!

(Photo by Sebastian Herrmann on Unsplash)

Posted on by:

mpodlasin profile

Mateusz Podlasin

@mpodlasin

I write in-depth JS articles at https://medium.com/@mpodlasin Follow me on Twitter at https://twitter.com/m_podlasin

Discussion

markdown guide
 

Async functions also return promises that resolve with the return value.

const wait = () => new Promise(resolve => {
    setTimeout(resolve, 3000);
});

const waitAndReturn = async () => {
    await wait();
    // throw new Error('oops!'); throwing rejects
    return 'hi';
};

So the last example can even be written like:

const createSomePromise = async () => {
  // make HTTP request
  return result;
};