Can't catch an unhandled Promise rejection from 3rd party library - javascript

I'm using a 3rd parth library to make an async call in my nodejs backend code. There's an unhandled promise rejection coming from this call which I'm having trouble catching. (It brings down my node app.)
Based on the input passed, it's expected to fail sometimes:
exports.getSomeData = (input) => {
console.log('GETTING DATA...')
return ThirdPartyLib.makeAsyncCall(input).then((result) => {
console.log('SUCCESS');
return result;
},(rejection) => {
console.log('REJECTED');
return {};
}).catch(error => {
console.log('ERROR');
return {};
});
}
But none of the REJECTED/ERROR messages print when it fails. I just see a console message from the lib: Unhandled rejection at: Promise and my app goes down.
Am I missing anything in the way I handle the non-happy path?
Is it possible for the library code to do something that the above WOULDN'T catch?

Is it possible for the library code to do something that the above WOULDN'T catch?
Sure, it just has to create a Promise (e.g. by calling an async function) without chaining it into the promise chain (aka without awaiting it):
async makeAsyncCall(data) {
Promise.reject("Possible");
}
So yes, the library you are using should either await that detached promise or it should attach an error handler to it (and handle / purposely ignore the error). It's not your fault.

Aside from the double rejection handler (which is pointless, just use catch()), your code should just work.
So given that you are seeing that error, this means that the library you are using has a bug.

Related

Why a promise reject is not catched within a try catch block but produces an Uncaught error?

I'm facing a promise rejection that escapes a try catch block. The rejection causes an Uncaught exception which is something I can not comprehend.
If I add a reject handler in the Promise.then or a Promise.catch, the rejection gets captured. But I was hopping try catch will work in this situation.
What is happening here?
class HttpResponse {
json() {
return Promise.reject('parse error')
}
}
function fetch() {
return new HttpResponse();
}
const res = fetch();
try {
res.json().then(json => {
alert(`json: ${json}`);
}
//,reason => {
// alert(`promise.reject ${reason}`);
//}
)
//.catch(reason => {
// alert(`promise.catch ${reason}`);
//})
} catch (e) {
alert(`catch{} from try catch: ${e}`);
}
Promises have their own mechanism of handling errors with the catch() method. A try / catch block can't control what's happening when chaining methods.
function fetch() {
return Promise.reject('parse error');
}
const res = fetch();
res.then(json => {
console.log(`json: ${json}`);
}).catch((e) => {
console.log(`catch{} from chained catch: ${e}`);
});
However, using async / await changes that. There you don't use the methods of a promise but handle errors with a try / catch block.
function fetch() {
return Promise.reject('parse error');
}
(async function() {
try {
const res = await fetch();
} catch (e) {
console.log(`catch{} from try catch: ${e}`);
}
})();
The technical reason behind this behavior is because a JavaScript promise invokes one of two callback functions for success or failure.
A promise does not emit an Error that is required for the try to work. it is not an Error (or more technically accurate) an instance of Error. It emits and event. You are encouraged to emit a new Error() if you need it to emit one. As pointed out here: https:developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
It emits an event that you can set up a handler for: https://developer.mozilla.org/en-US/docs/Web/API/PromiseRejectionEvent –
Finally, await throws and Error as described in the spec: https://tc39.es/ecma262/#await-rejected
Technical reasons behind:
HostPromiseRejectionTracker is an implementation-defined abstract
operation that allows host environments to track promise rejections.
An implementation of HostPromiseRejectionTracker must complete
normally in all cases. The default implementation of
HostPromiseRejectionTracker is to unconditionally return an empty
normal completion.
https://www.ecma-international.org/ecma-262/10.0/index.html#sec-host-promise-rejection-tracker
Basically javascript engines can freely implement this spec. In the case of browsers you don't get the Error captured inside the try/catch because the error is not emitted where you think it should be. But instead it's tracked with this special event that throws the error in the console.
Also on nodejs, this event causes the process to exit if you have node set to exit on unhandled exceptions.
On the other side, if you instead use async/await, the rejection is treated like an 'error' in practical terms. Meaning that the newer async/await feature behaves in a different fashion showing that it is not only syntactic sugar for Promises.
https://tc39.es/ecma262/#sec-throwcompletion
In sum, if you use Promise.then you are forced to provide a reject argument or chain it with .catch to have the rejection captured or else it will reach the console and in case of nodejs to exit the process if configured to do so (I believe new nodejs does this by default).
But if you use the newer async/await syntax you not only have a concise code (which is secondary) but a better rejection handling because it can be properly nested in a try/catch and it will behave like an standard Error.

Any problem with catch rejected promise and turn it into resolved with error code?

I am working on a project that always catch rejected promise, turns it into resolved one with error code, like this
return new Promise((resolve, reject) => {
axios({
...
}).then(res => {
resolve(res);
}).catch(err => {
let res = { ... }
res.error = err
resolve(res)
})
})
Then whenever call this api, rejected case is handled like this, i.e. without catch
axios_call(...).then(res => {
if (!res.err) {
//resolve case
} else {
//reject case
}
})
I never handle the rejected promise like this before so I am not sure will it cause any problem or it is just a different code style and works fine too.
I checked the possible duplicated q/a What is the explicit promise construction antipattern and how do I avoid it?
But I believed they are not the same. Because my question is about handling the rejected promise while that question is about deferred object, e.g. errors and rejections are not swallowed in my case.
First of all, avoid the Promise constructor antipattern! Your axios_call code should better look simply like this:
return axios({
...
}).catch(err => {
return { ..., error: err };
});
Then whenever call this api, rejected case is handled without catch. I never had handled the rejected promise like this before so I am not sure will it cause any problem or it is just a different code style and works fine too.
It works, but it's not fine. This style of error handling is weird and really non-idiomatic. It has the same problems as the traditional node-style callback API with separate error and result parameters:
the promise can't know whether you handled the error or not. You will not get any unhandled rejection warnings.
you always must write the code to deal with the res.error, if you want it or not. With normal promise usage, you could just supply separate onFulfill and onReject callbacks, and omitting the latter will get you the sensible default behaviour of forwarding the error instead of just dropping it.
I cannot see any advantages that your style would present, so I would recommend to avoid it and use normal promise rejections.

Prevent of “Uncaught (in promise)” warning. How to avoid of 'catch' block? ES6 promises vs Q promises

My question consist of two parts:
Part 1
According to standard ES6 Promise I see that I forced to use catch block everywhere, but it looks like copy/paste and looks weird.
Example:
I have some class which makes request to backend (lets call it API class).
And I have a few requirements for API class using:
1) I need to make requests in different parts of my application with single request errors processing:
// somewhere...
api.getUser().then(... some logic ...);
// somewhere in another module and in another part of app...
api.getUser().then(... some another logic...);
2) I want so 'then' blocks would work ONLY when 'getUsers' succeeded.
3) I don't want to write catch block everywhere I use api.getUsers()
api.getUser()
// I don't want following
.catch(e => {
showAlert('request failed');
})
So I'm trying to implement single error processing inside of the class for all "users requests"
class API {
getUser() {
let promise = makeRequestToGetUser();
promise.catch(e => {
showAlert('request failed');
});
return promise;
}
}
...but if request fails I still forced to use catch block
api.getUser()
.then(... some logic...)
.catch(() => {}) // <- I forced to write it to avoid of “Uncaught (in promise)” warning
... otherwise I'll get “Uncaught (in promise)” warning in console. So I don't know the way of how to avoid of .catch block everywhere I use api instance.
Seems this comes from throwing error in such code:
// This cause "Uncaught error"
Promise.reject('some value').then(() => {});
May be you can say 'just return in your class "catched" promise'.
class API {
getUser() {
return makeRequestToGetUser().catch(e => {
showAlert('request failed');
return ...
});
}
}
...but this contradict to my #2 requirement.
See this demo: https://stackblitz.com/edit/promises-catch-block-question
So my 1st question is how to implement described logic without writing catch block everywhere I use api call?
Part 2
I checked if the same API class implementation with Q library will get the same result and was surprised because I don't get “Uncaught (in promise)” warning. BTW it is more expectable behavior than behavior of native ES6 Promises.
In this page https://promisesaplus.com/implementations I found that Q library is implementation of Promises/A+ spec. But why does it have different behavior?
Does es6 promise respects Promises/A+ spec?
Can anybody explain why these libraries has different behavior, which one is correct, and how implement mentioned logic in case if "ES6 Promises implementation" is correct?
I see that I forced to use catch block everywhere
No, you don't need to do that. Instead, return the promise created by then to the caller (and to that caller's caller, and...). Handle errors at the uppermost level available (for instance, the event handler that started the call sequence).
If that's still too many catchs for you, you can hook the unhandledrejection event and prevent its default:
window.addEventListener('unhandledrejection', event => {
event.preventDefault();
// You can use `event.reason` here for the rejection reason, and
// `event.promise` for the promise that was rejected
console.log(`Suppressed the rejection '${event.reason.message}'`);
});
Promise.reject(new Error("Something went wrong"));
The browser will trigger that event prior to reporting the unhandled rejection in the console.
Node.js supports this as well, on the process object:
process.on('unhandledRejection', error => {
// `error` is the rejection reason
});
Note that you get the reason directly rather than as a property of an event object.
So I don't know the way of how to avoid of .catch block everywhere I use api instance.
Surely the caller of getUser needs to know it failed? I mean, if the answer to that is really "no, they don't" then the event is the way to go, but really the code using api should look like this:
function useTheAPI() {
return getUser()
.then(user => {
// Do something with user
});
}
(or the async equivalent) so that the code calling useTheAPI knows that an error occurred; again, only the top-level needs to actually handle the error.
Can anybody explain why these libraries has different behavior, which one is correct, and how implement mentioned logic in case if "ES6 Promises implementation" is correct?
Both are correct. Reporting unhandled exceptions entirely in userland (where libraries live) is hard-to-impossible to do such that there aren't false positives. JavaScript engines can do it as part of their garbage collection (e.g.: if nothing has a reference to the promise anymore, and it was rejected, and nothing handled that rejection, issue the warning).

Sinon, observing different behaviour using Promise.reject() to stub.rejects()

I was having issues running a test (in Node),
I was simulating a promise being rejected, and my code should retry (using promise-retry if that could be relevant).
When I simulated the rejected promise using stub.returns(Promise.reject(error)
I was getting a uncaught error warnings (for my dummyErrors), even though I am catching errors where I call my function...
-note, these uncaught errors were only happening in the unit tests not in real calls.
const mockedFunction = sinon.stub();
const dummyError = new Error('Document is locked');
mockedFunction.onCall(0).returns(Promise.reject(dummyError));
mockedFunction.onCall(0).returns(Promise.reject(dummyError));
mockedFunction.onCall(0).returns(Promise.reject(dummyError));
mockedFunction.onCall(1).returns(Promise.resolve({approved: true}));
I discovered that by changing to use the stub.rejects() syntax:
mockedFunction.onCall(0).rejects(dummyError);
mockedFunction.onCall(1).rejects(dummyError);
mockedFunction.onCall(2).rejects(dummyError));
mockedFunction.onCall(3).resolves({approved: true});
I no longer get the uncaught error warnings.
My issue is solved, however I would like to get a better understand as to why, I looked at the sinon source code and it looks like the implementation of .rejects is no different
In promise implementations that are intrinsically detect uncaught error (including V8/Node.js), an error from rejected promise should be caught on same tick, otherwise it triggers UnhandledPromiseRejectionWarning.
This will work fine:
let promise = Promise.reject();
promise.catch(() => {});
This will result in potentially unhandled promise and trigger a warning:
let promise = Promise.reject();
setTimeout(() => {
promise.catch(() => {});
});
If Promise.reject(dummyError) isn't chained with catch(...) or then(..., ...) on same tick, it triggers a warning, while in case of rejects(dummyError) a rejected promise is created on function call, so this likely will be true:
sinon.spy(Promise, 'reject');
mockedFunction.onCall(0).rejects(dummyError);
expect(Promise.reject).to.have.not.been.called;
mockedFunction();
expect(Promise.reject).to.have.been.called;
An alternative to rejects(dummyError) is:
mockedFunction.onCall(0).callsFake(() => Promise.reject(dummyError))

How to reject a promise i did not create?

Fairly new to promises here, and i was wondering..
After taking a look at bluebird package for handling promises:
Creating a Resolver
Resolve on success / reject on failure
Returning the created resolver promise property
I've been wondering how can i achieve the same effect of rejecting a promise to raise a catch using packages that already create the promise, and I'm just using their .then chain?
What i mean is, using reject on my create resolver will eventually raise a catch to the user of this function.
How can i raise a catch if i dont have the resolver. chaining the promise as follows :
function doSomthing(): Promise<someValue>
return somePackage.someMethodWithPromise().then((result)=> {
return someValueToTheNextThen;
})
}
The only way I've seen some packages achieving that is by returning a { errors, result } object so that the next then can check if there are any errors and react to it, but I want to raise a catch and not check for errors in every single then i have..
Hope i made myself clear, please let me know if anything is missing.
Thanks in advance for the help!
Just as in synchronous code, you can achieve this by throwing an error. This is the idiomatic thing to do when you're using promises and are already inside a then chain:
function doSomething(): Promise<someValue>
return somePackage.someMethodWithPromise().then((result)=> {
if (badResult(result)) {
throw new Error('Bad result!');
}
return someValueToTheNextThen;
})
}
doSomething()
.then(result => {
// this will get skipped if an error was thrown
})
.catch(error => {
// error will be caught here if one was thrown
console.error(error);
});
The .then just returns a Promise as well. You can simply created a rejected promise and return it from there if you determine there’s an error.
return somePackage.someMethodWithPromise().then(result => {
if (result.erroneous()) return Promise.reject('erroneous data');
return someValueToTheNextThen;
})
You can also simply throw anError; from the then which would also be catched in a catch block.

Categories

Resources