Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does my ES6 Promise Rejection need to be consumed immediately to avoid a console error message?

Please note: The following is an issue that behaves differently on different browsers. So perhaps this is a browser implementation issue. I would love some advice regardless.

In my application, I am creating a couple of promises that I may not be consuming until quite some time in the future. Which should be fine, they are promises, after all.

If the promise being stored away is resolved, there is no issue. I can consume it as far in the future as I want, and as many times as I want. As expected.

If the promise being stored away is rejected, however, there is an issue. Unless I consume that rejection shortly after it is made (not sure how shortly) a console message will crop up in Chrome or Firefox indicating that there is an uncaught promise rejection/error. IE does not pop up that error.

So consider the following code:

console.log("About to make a promise rejection.");
var foo = Promise.reject(new Error("Foo Rejected Promise."));
console.log("Promise rejection made.");

Note that there is no use or consumption of the promise foo. It is merely stored away for some future use.

The console on IE looks like this:

About to make a promise rejection.

Promise rejection made.

Which is expected. However, the same code on Chrome will yield the following console:

About to make a promise rejection.

Promise rejection made.

Uncaught (in promise) Error: Foo Rejected Promise.(…)

Firefox looks pretty much like Chrome, other than the wording around the "uncaught" error.

But the thing is that I intend to handle this "error" much later, at the time I am consuming the promise. Merely HAVING a rejected promise should not cause a console error..... that should happen if I consume the promise and don't handle the error.

To simulate a "much later" handling of the error, consider this alteration of the code:

console.log("About to make a promise rejection.");
var foo = Promise.reject(new Error("Foo Rejected Promise."));
console.log("Promise rejection made.");
setTimeout(() => {
    foo.catch((err) => {
        console.log("Displaying " + err.message + " 10 seconds later.");
    });
}, 10000);

Now in this case, we "handle" the error by displaying something on the console after a timeout. Now IE still handles this as I would expect:

About to make a promise rejection.

Promise rejection made.

Displaying Foo Rejected Promise. 10 seconds later.

In this case, Firefox does like IE, and displays exactly these messages, and does not give me an erroneous console error.

Chrome, however, still gives the erroneous error:

About to make a promise rejection.

Promise rejection made.

Uncaught (in promise) Error: Foo Rejected Promise.(…)

Displaying Foo Rejected Promise. 10 seconds later.

So Chrome both complained about my error not being handled, and then displayed that it was handled.

It appears that I can get around all this with the following code that that seems like a hack. Basically I do a "fake" handling of the error when the promise is created and then really handle it the way I want to later:

console.log("About to make a promise rejection.");
var foo = Promise.reject(new Error("Foo Rejected Promise."));
foo.catch(() => { });  // Added this hack-ish code.
console.log("Promise rejection made.");
    setTimeout(() => {
    foo.catch((err) => {
        console.log("Displaying " + err.message + " 10 seconds later.");
    });
}, 10000);

But this is ugly code.

My question is twofold - is there some way of looking at what Chrome (and to some extent FireFox) is doing and think of it as a favor? Because to me it seems awful. And secondly, is there a better way of getting around this than the hack of pretending to handle an error when you aren't?

Thanks in advance.

like image 755
Stephan G Avatar asked Aug 03 '16 01:08

Stephan G


People also ask

What happens when you reject a Promise Javascript?

The static Promise. reject function returns a Promise that is rejected. For debugging purposes and selective error catching, it is useful to make reason an instanceof Error .

What happens when Promise is rejected?

If the Promise rejects, the second function in your first . then() will get called with the rejected value, and whatever value it returns will become a new resolved Promise which passes into the first function of your second then.

How do you handle rejection promises?

We must always add a catch() , otherwise promises will silently fail. In this case, if thePromise is rejected, the execution jumps directly to the catch() method. You can add the catch() method in the middle of two then() methods, but you will not be able to break the chain when something bad happens.

Why do promises get rejected?

A Promise rejection indicates that something went wrong while executing a Promise or an async function. Rejections can occur in several situations: throwing inside an async function or a Promise executor/then/catch/finally callback, when calling the reject callback of an executor , or when calling Promise.


1 Answers

Any promise that has a chance to be rejected, should be handled, similarly to exceptions.

There are not so many chances that a promise won't be chained synchronously. But if it won't, it requires 'hack-ish' .catch(() => { }) to synchronously handle the rejection.

Chrome promise rejection behaviour puts this

var foo = Promise.reject(new Error("Foo Rejected Promise."));
foo.catch(() => { });  // Added this hack-ish code.

setTimeout(() => {
    foo.catch((err) => {
        console.log("Displaying " + err.message + " 10 seconds later.");
    });
}, 10000);

into the same error-handling boat as

try {
  throw new Error("Foo"));

  setTimeout(() => {
    try {
      throw new Error("Foo"));
    } catch (err) {
      console.log("Displaying " + err.message + " 10 seconds later.");
    }
  }, 10000);
} catch (err) {}

Wouldn't the exception wait 10 seconds to be caught by next try...catch before being thrown? It won't wait even a single tick. It requires 'hack-ish' try { ... } catch (err) {} block to perform as intended.

This behaviour is already well-known to Bluebird library users for quite a long time. Bluebird has adjustable error handling and allows to debug promises efficiently even on the large scale.

The behaviour is also known in Angular 2 development, it is forced for promises with Zone.js library.

Since promise debugging has limited value and applicable to development environment, it is possible to modify or disable this behaviour for native promises in production builds:

if (typeof DEBUG_PROMISE === 'undefined') {
  window.addEventListener('unhandledrejection', (e) => {
    e.preventDefault();
    console.warn(e.reason);
  });
}

Here's more reading on the subject.

like image 61
Estus Flask Avatar answered Oct 09 '22 09:10

Estus Flask