Promises rules in Javascript, even now with the introduction of async/await, are essential knowledge for all JS developers.

But Javascript handles asynchrony differently than other programming languages. As a result, even experienced developers sometimes fall into traps. I’ve personally seen good Python or Java programmers make stupid mistakes when coding for Node.js or browsers.

To avoid these errors, there are many subtle issues with Promises in Javascript that must be recognized. Some of these are purely stylistic, but many are hard-to-track errors that can actually be introduced. So I decided to compile a list of the three most common mistakes developers make when programming with Promises.


Wrap everything in the Promise constructor

The first mistake is one of the most obvious, but I’ve seen developers make it surprisingly often.

Promises When you first learn about Promises, you will learn about the Promise constructor. The constructor can be used to create a new Promise object.

Perhaps because people often start learning by wrapping some browser API (such as setTimeout) in a Promise constructor, it’s ingrained in their minds that the only way to create a Promise object is to use a constructor.

Therefore, they usually write code like this:

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

As you can see, in order to do something with the result of somePreviousPromise, someone used then, but then decided to wrap it again in a Promise constructor, To store the result of the operation in the variable of createdPromise, presumably for more operations on the Promise later.

This is clearly not necessary. The whole point of the then method is that it itself returns a Promise, which represents the callback function in the THEN after executing somePreviousPromise. The argument for then is the result of the successful execution of somePreviousPromise.

So, the above code is roughly equivalent to:

const createPromise = somePreviousPromise.then(result= > {
	// Do something with result
	return result
})
Copy the code

Doesn’t it look better?

But why do I say it’s only roughly equivalent? What’s the difference?

It can be hard to spot if you’re inexperienced and unobservant, but there’s a huge difference in error handling that’s more important than the verbosity of the first piece of code.

Suppose that somePreviousPromise fails for some reason and throws an error. For example, this Promise sends an HTTP request and the API responds with 500 errors.

It turns out that in the previous code, we wrapped one Promise into another Promise, and we couldn’t catch the error at all. To fix this, we must make the following changes:

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

We simply add a reject parameter to the callback function and use it by passing it as a second parameter to the THEN method. It is important to remember that the THEN method accepts a second optional argument for error handling.

Now if somePreviousPromise fails for some reason, the Reject function will be called, and we’ll be able to handle the error on createdPromise as usual.

Will that solve all our problems? Sorry I can’t.

We handled the error that somePreviousPromise itself might have, but we still had no control over what happened in the callback function that was the first argument to the then method. The code that does something to result in the comment area // may have some errors, and if the code in this area throws any errors, the second argument to the THEN method will not catch them.

This is because the error handler, which is the second argument to the THEN method, responds only to errors that occur before the current THEN on the Promise chain.

Therefore, the appropriate (and final) solution would be this:

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

Notice that this time we use the catch method — because it will be called after the first then, it will catch all the errors thrown on the Promise chain above it. Whether the callback in somePreviousPromise or then fails, the Promise handles those cases as expected.

As you can see, there are a number of nuances when wrapping code in the Promise constructor. This is why it is best to use the then method to create new Promises, as shown in the second code. Not only does it look better, but it can help us avoid those extremes.


Comparison of serial call THEN and parallel call THEN

Because many programmers have an object-oriented programming background, it is natural for them to have a method that changes an object rather than creating a new one.

This is probably why I’ve seen people get confused about what happens when the then method is called on a Promise.

Compare the following two codes:

const somePromise = createSomePromise();

somePromise
  .then(doFirstThingWithResult)
  .then(doSecondThingWithResult);
Copy the code
const somePromise = createSomePromise();

somePromise
  .then(doFirstThingWithResult);

somePromise
  .then(doSecondThingWithResult);
Copy the code

Do they do the same thing? It seems that way, after all, both pieces of code call then twice on somePromise, right?

No, this is a very common myth. In fact, the two pieces of code do completely different things. If you don’t fully understand what you’re doing in both pieces of code, it can lead to tricky errors.

As we discussed in the previous section, the THEN method creates a completely new, independent Promise. This means that in the first piece of code, the second then method is called not on somePromise, but on a new Promise object, This code calls doFirstThingWithResult as soon as somePromise state becomes successful. Then add a callback operation, doSecondThingWithResult, to the newly returned Promise instance

In fact, the two callbacks will be executed one after the other — ensuring that the second callback is invoked only after the first callback has completed without any problems. In addition, the first callback will take the value returned by somePromise as an argument, but the second callback will take the value returned by doFirstThingWithResult as an argument.

On the other hand, in the second code, we call the then method twice on somePromise, essentially ignoring the two new Promises objects returned from that method. Because THEN is called twice on the exact same Promise instance, we can’t determine which callback to execute first, and the order of execution here is uncertain.

In a sense, the two callbacks should be independent and not dependent on any previously invoked callbacks, which I sometimes refer to as “parallel” execution. But, of course, in reality, the JS engine can only perform one function at a time — you simply don’t know in what order they’ll be called.

The second difference between the two pieces of code is that in the second piece of code both doFirstThingWithResult and doSecondThingWithResult receive the same argument — the result of a successful execution of somePromise, The return values of the two callback functions are completely ignored in this example.


Execute the Promise immediately after it is created

The reason for this myth is that most programmers have a lot of experience with object-oriented programming.

In object-oriented programming thinking, it is generally considered a good practice to ensure that an object’s constructor does nothing by itself. For example, an object representing a database should not initiate a link to the database when its constructor is called using the new keyword.

Instead, it is better to provide a specific method — such as one called init — that will explicitly create the connection. In this way, an object does not perform any unexpected operations just because it has been started. It waits patiently for an explicit request from the programmer to perform an action.

But that’s not how Promises works.

Consider the following example:

const somePromise = new Promise(resolve= > {
  // Create an HTTP request
  resolve(result);
});
Copy the code

You might think that the function that makes the HTTP request is not called here because it is wrapped in the Promise constructor. In fact, many programmers expect the THEN method to be invoked on somePromise after it is executed.

But that’s not the case. Once the Promise is created, the callback is executed immediately. This means that by the time you get to the next line after creating the somePromise variable, your HTTP request may have been executed, or at least in the execution queue.

We say a Promise is “urgent” because it executes its associated action as quickly as possible. In contrast, many people expect Promises to be “lazy,” meaning only when absolutely necessary (for example, when the then method is first invoked on a Promise). This is a myth. Promise is always eager, never lazy.

But what do you do if you want to postpone implementing a Promise? What if you want to defer making the HTTP request? Is there some fancy mechanism built into Promises that allows you to do something like this?

The answers sometimes exceed developers’ expectations. Functions are an inert mechanism. They are executed only if the programmer explicitly calls them using the () notation. Just defining a function doesn’t actually do anything. Therefore, the best way to make promises “lazy” is simply to wrap them in functions!

Take a look at the following code:

const createSomePromise = () = > new Promise(resolve= > {
  // Create an HTTP request
  resolve(result);
});
Copy the code

Now we wrap the Promise constructor invocation in a function. So, in fact, it hasn’t really been called yet. We also changed the variable name from somePromise to createSomePromise because it is no longer a Promise object — it is a function that creates and returns a Promise object.

The Promise constructor (and the callback function with the HTTP request) is called only when the function is executed. So now we have a lazy Promise to implement only when we really want it to.

Also, note that it provides another function along with it. We could easily create another Promise object that performs the same action.

If, for some strange reason, we want to make two of the same HTTP requests and execute them at the same time, we simply call the createSomePromise function twice, one after the other. Alternatively, if the request fails for any reason, we can re-request it using the same function.

This shows how convenient it is to package Promises in functions (or methods), so this pattern should come naturally to JavaScript developers.

Ironically, if you read my article Promises VS Observables, you will know that programmers introduced to Rx.js often make the opposite mistake. They code Observables as if they are eager (like Promises) when in fact they are lazy. So, for example, wrapping Observables in functions or methods is often meaningless and, in fact, harmful.


conclusion

This article illustrates three types of mistakes I see developers make all the time: Promises in JavaScript are just scratching the surface.

Have you ever encountered any interesting types of bugs in your code or someone else’s code? If so, please leave a comment in the comments section.

Thank you for reading!