ES6 Promises Error Handling - javascript

I'm currently reading "YKDJS - Async & Performance" by Kyle Simpson, in particular Chapter 3 - Promises.
The author says that any Promise for which no rejection handler is registered receives a default one:
let p = new Promise(function(resolve, reject) { resolve("Yay!"); });
p.then(
function(val) { /* Do whatever */ }
/* Default rejection handler
, function(e) { throw e; } */
);
Later in the chapter he claims that one problem with the way Promises are designed is the following:
In any Promise chain, any error that happens in one of the handler functions of the last Promise in the chain is just "swallowed" instead of being reported.
He proposes to change the way Promises work so that any Promise that doesn't have a rejection handler reports the error by throwing a global error. Then he proposes a theoretical Promise#defer() function one could use on a Promise to prevent this reporting behavior.
Now my question is: How do these two go together? It's true that any Promisethat doesn't have a rejection handler receives a default one which just throws the rejection value as a global error:
Promise.reject("Oops");
/* VM668:1 Uncaught (in promise) Oops */
So already Promises seem to work in just the way he proposes.
Am I misunderstanding something? Thanks for any help.

The Uncaught Handling he mentions in
Some Promise libraries have added methods for registering something
like a "global unhandled rejection" handler, which would be called
instead of a globally thrown error. But their solution for how to
identify an error as "uncaught" is to have an arbitrary-length timer,
say 3 seconds, running from time of rejection. If a Promise is
rejected but no error handler is registered before the timer fires,
then it's assumed that you won't ever be registering a handler, so
it's "uncaught."
In practice, this has worked well for many libraries, as most usage
patterns don't typically call for significant delay between Promise
rejection and observation of that rejection.
has been standardised as unhandled rejection warnings (not using an arbitrary timer, but firing right away). It does indeed work quite well.
He also states in Chapter 4 of ES6 & Beyond
[When] we are not listening for that rejection, […] it will be
silently held onto for future observation. If you never observe it by
calling a then(..) or catch(..), then it will go unhandled. Some
browser developer consoles may detect these unhandled rejections and
report them, but this is not reliably guaranteed; you should always
observe promise rejections.

Related

Difference of exception and unfulfilled promise?

I am reading about error handling, and the book says
If a throw occurs inside a Promises then handler function, then it is
a rejection. One way to think about exceptions and rejections is that exceptions are synchronous errors and rejections are asynchronous errors.
Questions
The book writes rejection, but shouldn't that be unfulfilled?
I were under the impression that a throw is always an exception?
And why are exceptions only for synchronous code?
An unfulfilled Promise is simply one that hasn't been fulfilled, which is quite possible even if it doesn't reject. For example:
const prom = new Promise((resolve) => {
// code here that never calls `resolve`
});
That is a Promise that will never be fulfilled - but it won't reject either.
An "unfulfilled" Promise may not necessarily hang forever like the above - it's simply a Promise that hasn't resolved yet.
a throw is always an exception?
To an extent, yes, though it behaves somewhat differently when inside a Promise - it won't cause an error event. Instead, it'll try to find a .catch handler in the Promise chain it's currently in, and if it doesn't find one, an unhandledrejection event will be fired.
And why are exceptions only for synchronous code?
That's just how the language was designed.
I think you need to understand the reason why rejections are needed, the problem it tries to solve. Consider a simple example such as this:
try {
iDoNotExist
} catch(e) {
//e ReferenceError
}
Pretty simple, the code is being evaluated and "tried" for errors. There's such an error and well, it gets caught.
Now, what if we changed it a little bit into this:
try {
setTimeout(function(){ iDoNotExist }, 1000);
} catch(e) {
//e ReferenceError
}
If you run this code, you'll notice that your javascript environment will emit an Error. It's not being caught here. Why? Because the code that throws the error is not being run when it's being "tried", only the setTimeout function which simply schedules the function and well, that function is doing it's job, it's scheduling it correctly. This in turn is the same reason why so many people get it wrong when they try accessing a value that's product of an async function. E.g.
let result;
setTimeout(() => result = true, 1000);
console.log(result) // Why is this undefined? - Famous last question
Enter promises to the rescue, which not only allow you to recover a value which you would typically return, it also lets you recover any potential error that happens on said async operations. Providing you with semantics that are similar with synchronous code (even more when paired with async functions).
So, I would say rejections are complementary to exceptions. To preserve your program flow when there's an asynchronous boundary (just like fulfilled promises do to preserve function composition).
As for what you would call unfulfilled well... there's a couple of things. unfulfilled means "not fulfilled". A rejected promise IS, technically, an unfulfilled promise. However, an unresolved promise (a promise in a pending state) is ALSO an unfulfilled promise. Technically.
Most people will think of the latter but really it could be either. Mostly because it comes from a time where "fulfill" and "resolve" where used interchangeably (which is wrong). But also because that's typically the expected result of a promise resolution.
Under no circumstance is an unresolved promise a rejected promise. An unresolved promise is in a transitory state in which it could transit into either a fulfilled promise or a rejected one.

Should I refrain from handling Promise rejection asynchronously?

I have just installed Node v7.2.0 and learned that the following code:
var prm = Promise.reject(new Error('fail'));
results in this message:;
(node:4786) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: fail
(node:4786) DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
I understand the reasoning behind this as many programmers have probably experienced the frustration of an Error ending up being swallowed by a Promise. However then I did this experiment:
var prm = Promise.reject(new Error('fail'));
setTimeout(() => {
prm.catch((err) => {
console.log(err.message);
})
},
0)
which results in:
(node:4860) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: fail
(node:4860) DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
(node:4860) PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1)
fail
I on basis of the PromiseRejectionHandledWarning assume that handling a Promise rejection asynchronously is/might be a bad thing.
But why is that?
Note: See 2020 updates below for changes in Node v15
"Should I refrain from handling Promise rejection asynchronously?"
Those warnings serve an important purpose but to see how it all works see those examples:
Try this:
process.on('unhandledRejection', () => {});
process.on('rejectionHandled', () => {});
var prm = Promise.reject(new Error('fail'));
setTimeout(() => {
prm.catch((err) => {
console.log(err.message);
})
}, 0);
Or this:
var prm = Promise.reject(new Error('fail'));
prm.catch(() => {});
setTimeout(() => {
prm.catch((err) => {
console.log(err.message);
})
}, 0);
Or this:
var caught = require('caught');
var prm = caught(Promise.reject(new Error('fail')));
setTimeout(() => {
prm.catch((err) => {
console.log(err.message);
})
}, 0);
Disclaimer: I am the author of the caught module (and yes, I wrote it for this answer).
Rationale
It was added to Node as one of the Breaking changes between v6 and v7. There was a heated discussion about it in Issue #830: Default Unhandled Rejection Detection Behavior with no universal agreement on how promises with rejection handlers attached asynchronously should behave - work without warnings, work with warnings or be forbidden to use at all by terminating the program. More discussion took place in several issues of the unhandled-rejections-spec project.
This warning is to help you find situations where you forgot to handle the rejection but sometimes you may want to avoid it. For example you may want to make a bunch of requests and store the resulting promises in an array, only to handle it later in some other part of your program.
One of the advantages of promises over callbacks is that you can separate the place where you create the promise from the place (or places) where you attach the handlers. Those warnings make it more difficult to do but you can either handle the events (my first example) or attach a dummy catch handler wherever you create a promise that you don't want to handle right away (second example). Or you can have a module do it for you (third example).
Avoiding warnings
Attaching an empty handler doesn't change the way how the stored promise works in any way if you do it in two steps:
var prm1 = Promise.reject(new Error('fail'));
prm1.catch(() => {});
This will not be the same, though:
var prm2 = Promise.reject(new Error('fail')).catch(() => {});
Here prm2 will be a different promise then prm1. While prm1 will be rejected with 'fail' error, prm2 will be resolved with undefined which is probably not what you want.
But you could write a simple function to make it work like a two-step example above, like I did with the caught module:
var prm3 = caught(Promise.reject(new Error('fail')));
Here prm3 is the same as prm1.
See: https://www.npmjs.com/package/caught
2017 Update
See also Pull Request #6375: lib,src: "throw" on unhandled promise rejections (not merged yet as of Febryary 2017) that is marked as Milestone 8.0.0:
Makes Promises "throw" rejections which exit like regular uncaught errors. [emphasis added]
This means that we can expect Node 8.x to change the warning that this question is about into an error that crashes and terminates the process and we should take it into account while writing our programs today to avoid surprises in the future.
See also the Node.js 8.0.0 Tracking Issue #10117.
2020 Update
See also Pull Request #33021: process: Change default --unhandled-rejections=throw (already merged and released as part of the v15 release - see: release notes) that once again makes it an exception:
As of Node.js 15, the default mode for unhandledRejection is changed to throw (from warn). In throw mode, if an unhandledRejection hook is not set, the unhandledRejection is raised as an uncaught exception. Users that have an unhandledRejection hook should see no change in behavior, and it’s still possible to switch modes using the --unhandled-rejections=mode process flag.
This means that Node 15.x has finally changed the warning that this question is about into an error so as I said in 2017 above, we should definitely take it into account while writing our programs because if we don't then it will definitely cause problems when upgrading the runtime to Node 15.x or higher.
I assume that handling a Promise rejection asynchronously is a bad thing.
Yes indeed it is.
It is expected that you want to handle any rejections immediately anyway. If you fail to do (and possibly fail to ever handle it), you'll get a warning.
I've experienced hardly any situations where you wouldn't want to fail immediately after getting a rejection. And even if you need to wait for something further after the fail, you should do that explicitly.
I've never seen a case where it would be impossible to immediately install the error handler (try to convince me otherwise). In your case, if you want a slightly delayed error message, just do
var prm = Promise.reject(new Error('fail'));
prm.catch((err) => {
setTimeout(() => {
console.log(err.message);
}, 0);
});

Exception gets swallowed in promise chain

I noticed something very strange happening when an exception is thrown inside a chain of promises in Parse for React Native. The promise chain never resolves, and never rejects, and the exception is never thrown. It just disappears silently.
Here's sample code to recreate the problem:
// Replacing this with Promise.resolve() prints the error.
// Removing this stage prints the error.
Parse.Promise.as()
// Removing this stage causes a red screen error.
.then(function() {
// Replacing this with Parse.Promise.as() causes a red screen error.
return Promise.resolve();
})
.then(function () {
throw new Error("There was a failure");
})
.then(function () { console.log("Success")}, function (err) { console.log(err) });
As you can see from the comments, it only seems to happen in this particular sequence of events. Removing a stage, or swapping a Parse promise for a native JS promise, causes things to behave again. (In my actual code, the "Promise.resolve()" stage is actually a call into a native iOS method that returns a promise.)
I'm aware that Parse promises don't behave exactly like A+ compliant promises (cf. https://stackoverflow.com/a/31223217/2397068). Indeed, calling Parse.Promise.enableAPlusCompliant() before this section of code causes the exception to be caught and printed. But I thought that Parse promises and native JS promises could be used together safely.
Why is this exception disappearing silently?
Thank you.
Why is this exception disappearing?
Parse does by default not catch exceptions, and promises do swallow them.
Parse is not 100% Promises/A+ compatible, but nonetheless it does try to assimilate thenables that are returned from then callbacks. And it does neither catch exceptions nor executes its own callbacks asynchronously.
What you are doing can be reproduced without then using
var p1 = new Parse.Promise();
var p2 = new Parse.Promise();
// p2 should eventually settle and call these:
p2._resolvedCallbacks = [function (res) { console.log("Success") }];
p2._rejectedCallbacks = [function (err) { console.log(err) }];
function handler() {
throw new Error("There was a failure");
}
// this is what the second `then` call sets up (much simplified):
p1._resolvedCallbacks = [function(result) {
p2.resolve(handler(result)); // throws - oops
}];
// the native promise:
var p = Promise.resolve();
// what happens when a callback result is assimilated:
if (isThenable(p))
p.then(function(result) {
p1.resolve(result);
});
The problem is that p1.resolve is synchronous, and executes the callbacks on p1 immediately - which in turn does throw. By throwing before p2.resolve can be called, p2 will stay forever pending. The exceptions bubbles up and becomes the completion of p1.resolve() - which now throws in a callback to a native then method. The native promise implementation catches the exception and rejects the promise returned by then with it, which is however ignored everywhere.
silently?
If your "native" promise implementation supports unhandled rejection warnings, you should be able to see the exception hanging around in the rejected promise.
For your consideration in addition to technical reasons provided in your quoted answer:
Compliant promises
ES6/A+ compliant Promise instances share:
When a promise is settled, its settled state and value are immutable.
A promise cannot be fulfilled with a promise.
A then registered 'fulfill' listener is never called with a promise (or other thenable) object as argument.
Listeners registered by then are executed asynchronously in their own thread after the code which caused them to be executed has run to completion.
Irrespective of whether a listener was registered for call back whan a promise becomes settled ( 'fulfilled' or 'rejected'),
the return value of a listener is used to fulfill resolve the promise returned by its then registration
a value thrown (using throw) by a listener is used to reject the promise returned by then registration, and
A promise resolved with a Promise instance synchronizes itself with the eventual settled state and value of the promise provided as argument. (In the ES6 standard this is described as "locked in").
A promise resolved with a thenable object which is not a Promise instance will skip synch as required: if at first resolved with a thenable which "fulfills" itself with a thenable, the Promise promise will re-synchronize itself with the most recent 'thenable' provided. Skipping to a new thenable to synchronize with cannot occur with A+ promises because they never call a "fulfilled" listener with a thenable object.
Non Compliant promises
Potential characteristics of non compliant promise like objects include
allowing the settled state of a promise to be changed,
calling a then 'fulfilled' listener with a promise argument,
calling a then listener, either for 'fulfilled' or 'rejected' states, synchronously from code which resolves or rejects a promise,
not rejecting a then returned promise after a listener registered in the then call throws an exception.
Interoperability
Promise.resolve can be used to settle its returned promise with a static value, perhaps mainly used in testing. Its chief purpose, however, is to quarantine side effects of non compliant promises. A promise returned by Promise.resolve( thenable) will exhibit all of behaviours 1-7 above, and none of the non compliant behaviours.
IMHO I would suggest only using non A+ promise objects in the environment and in accordance with documentation for the library which created them. A non compliant thenable can be used to to resolve an A+ promise directly (which is what Promise.resolve does), but for full predictability of behaviour it should be wrapped in a Promise object using Promise.resolve( thenable) before any other use.
Note I tried to test Parse promises for A+ compliance but it does not seem to provide a constructor. This makes claims of "almost" or "fully" A+ compliance difficult to quantify. Thanks to zangw for pointing out the possibility of returning a rejected promise form a listener as an alternative to throwing an exception.

Can you "plug in" to unhandled Promise rejections in Chrome? [duplicate]

This question already has answers here:
Catch all unhandled javascript promise rejections
(4 answers)
Closed 7 years ago.
A while back, v8 gained the capability to detect Promises that are rejected but have no handlers attached (commit). This landed in Chrome as a nice console error, especially useful for when you've made a typo or forget to attach a handler:
I would like to add a handler to take some action (e.g., reporting to an error reporting service) when this happens, similar to the uncaught exception pattern:
window.addEventListener("error", handler);
Alternatively, I'm looking for any mechanism that I can use to automatically invoke some sort of callback when a promise is rejected but not handled on that tick.
Until window.addEventListener('unhandledrejection', e => ...) is here you may hack your own Promise constructor which creates original Promise and calls catch on it passing:
error => {
var errorEvent = new ErrorEvent('error', {
message: error.message,
error: error
});
window.dispatchEvent(errorEvent); // For error listeners.
throw error; // I prefer to see errors on the console.
}
But it seems we have to patch then, catch and Promise.reject also -- lots of work.
Someone may want to write a polyfill to emit custom unhandledrejection event in such cases.

Rejected promises in Protractor/WebDriverJS

WebDriverJS and Protractor itself are entirely based on the concept of promises:
WebDriverJS (and thus, Protractor) APIs are entirely asynchronous. All
functions return promises.
WebDriverJS maintains a queue of pending promises, called the control
flow, to keep execution organized.
And, according to the definition:
A promise is an object that represents a value, or the eventual
computation of a value. Every promise starts in a pending state and
may either be successfully resolved with a value or it may be rejected
to designate an error.
The last part about the promise rejection is something I don't entirely understand and haven't dealt with in Protractor. A common pattern we've seen and written is using then() and providing a function for a successfully resolved promise:
element(by.css("#myid")).getAttribute("value").then(function (value) {
// do smth with the value
});
The Question:
Is it possible that a promise returned by any of the Protractor/WebDriverJS functions would not be successfully resolved and would be rejected? Should we actually worry about it and handle it?
I've experienced a use-case of promise rejection while using browser.wait(). Here is an example:
var EC = protractor.ExpectedConditions;
function isElementVisible() {
var el = element(by.css('#myel'));
// return promise
return browser.wait(EC.visibilityOf(el), 1000)
.then(function success() {
return true; // return if promise resolved
}, function fail() {
return false; // return if promise rejected
});
}
expect(isElementVisible()).toBe(true);
expect(isElementVisible()).toBe(false);
Here, if element is on a page, success will be executed, otherwise, if it is not found when 1 second passes, then fail will be called. My first point is that providing a callback for rejection gives an ability to be consistent with what one should expect. In this case I am kinda sure that promise will always resolve to true or false, so I can build a suite relying on it. If I do not provide a fail callback, then I'll get an Uncaught exception because of timeout, which will still fail my particular spec and still run the rest of the specs. It won't be uncaught by the way, Protractor is gonna catch it, but here I want to bring a second point, that Protractor is considered a tool which you use to write and run your code, and if an exception is caught by Protractor, then this exception has left your code unhandled and your code has a leak. But ... at the same time I do not think that one should waste time to catch everything in tests: if there is no element on a page or click has failed, then a respective spec will obviously fail too, which is fine in most of the cases. Unless you want to use the result of failure to build some code on top of it like it is in my sample.
That is the great thing about promises you are going to get a response, either an response of data or an error message. That extended to a series of promises like Webdriver uses you are going to get an array of responses or a failure response of the first one that fails. How you handle the failed response is up to you I usually just dump it into a log for the console to see what failed. The only thing you need to figure out is do you abort the rest of your tests or do you continue on.
Here is a decent article on the subject as well. http://www.toolsqa.com/selenium-webdriver/exception-handling-selenium-webdriver/
Fyi what you are doing is fine you are just never bothering to catch any of the errors though, I am not sure if that matters to you or not, you could also abstract the call in a function to auto handle the errors for you if you wanted to log them somewhere.

Categories

Resources