I understand when the UnhandledPromiseRejectionWarning error happens.
What I don't understand is why the the spec was written in such a way as to allow this behavior.
For example, the following code is bad, if callApi() rejects than we get the UnhandledPromiseRejectionWarning error in Node a similar error in each browser:
class A {
constructor () {
this._stuff = Promise.reject(Error('oops'))
}
getStuff () {
return this._stuff
}
}
const a = new A()
setTimeout(() => {
a.getStuff().catch(err => console.log('I caught it!'))
}, 100)
But why?! I tend to thing of promises as abstracting over when stuff happens.
That's exactly how the promise acts if callApi() is successful: the caller of getStuff() will get the stuff when the data is available.
But the rejection case doesn't work the same way. What I'd expect to happen (if I hadn't been burned by this before) is for the burden of handling the error to fall to the caller of getStuff. That's not how it works, but why?
The detection of an unhandled promise rejection is imperfect in node.js. I don't know exactly how it works internally, but if you don't have a .catch() in a promise chain and that promise rejects, even if you assign it to a variable and then later add a .catch() to it, then it will give you the "unhandled rejection warning".
In your specific case, it is probably because you got back to the event loop with a rejected promise that wasn't caught - your .catch() is only added later on a subsequent event. There's no way for the node.js interpreter to know that you're going to do that. It sees that an event finished executing and a rejected promise was not handled so it creates the warning.
To avoid the warning, you will have to code it differently. You don't really show a real world use so we can see what you're trying to accomplish to make a specific suggestion. As with other types of things like this, the best way to handle it varies depending upon the real world use case.
If you're asking why an unhandled rejection is a warning at all, then that's because it can be a serious error and if there was no warning, then it would simply fail silently and the developer would never be the wiser. It's very much akin to an unhandled exception in synchronous code. That's a serious error and aborts the program. node.js has been notifying for awhile that an unhandled rejection may get the same treatment in the future. For now, it's just a warning.
Related
If I enter the following into the console, no error is reported to the console
let p = new Promise(function(resolve, reject) {
console.log('started')
reject('immediately reject')
})
console.log('do some other work')
p.catch(function(error) {
console.log('error caught')
})
// Outputs:
// do some other work
// error caught
But if I remove the call to catch an uncaught error is shown. I can even type in the first half, hit enter, see the error, then add the catch and the error goes away. This seems weird to me: how can JavaScript know that a promise will eventually be caught? How would this affect control flow of an application if there's always a chance a promise could later be caught?
I understand promises are typically for asynchronous code, but one could imagine a promise which may return immediately with a validation error or something.
I am aware of try/catch, I'm just trying to understand how this works.
Running in Microsoft Edge 91.0.864.59 (64-bit)
If you're just talking about a message you see in the console that then disappears, then this is just something that occurs in a specific Javascript environment where it decides after-the-fact to rescind/remove a debug message in the console. It does not affect the running of your code in any way. Try three different browsers and you will see different behaviors in each because what shows in the console and when it shows there is up to the implementor of the engine/console, not something that is standardized or something that affects the outcome of your code.
Beyond that, let's discuss issues related to the timing of when a .catch() handler is added. The rejection of your promise is not processed synchronously. The promise state is changed immediately internal to the promise, but it does not synchronously call any .catch() handlers. Instead, a job is inserted into the promise job queue and only when the current chunk of Javascript that is executing is finished and returns control back to the event loop does the promise job get to do its work.
So, in your code, the .catch() handler is added in the current chunk of Javascript execution BEFORE the promise tries to call its catch handlers. Thus, when the promise job that contains the rejection does actually get processed, it is not an uncaught promise because the .catch() handler is already in place.
FYI, typing code into the console and executing it there will not necessarily offer the same timing (and thus errors) as running code in a real script. My comments above are about what happens if you run your whole script at once. I always evaluate asynchronous code in a real execution environment running a complete script, not by typing code into a console and running it pieces at a time.
I can even type in the first half, hit enter, see the error, then add the catch and the error goes away.
That's just a weirdness that occurs in the console when you run pieces of code, but not the whole script. That is not representative of running the whole code in a script.
This seems weird to me: how can JavaScript know that a promise will eventually be caught?
It doesn't. It evaluates whether there's a .catch() handler when the rejection is actually processed (which is via the Promise job queue).
How would this affect control flow of an application if there's always a chance a promise could later be caught?
It's not really an issue because the .catch() handler just needs to be in place before control returns to the event loop when the promise is actually rejected. And, that is usually how code is written so this isn't an issue. You create the promise (or call a function that returns a promise), then you add handlers to it - all in one body of code.
I understand promises are typically for asynchronous code, but one could imagine a promise which may return immediately with a validation error or something.
Neither .then() or .catch() handlers are ever called synchronously, even if the promise is resolved or rejected synchronously. They are always called via the Promise job queue which is always after the current synchronously running Javascript finishes executing and returns control back to the event loop.
Coming from a python async background, in python it's very important to always keep track of async tasks (promises). The runtime gives errors for "floating" async tasks with no references. In Javascript, though, in some cases it seems perfectly OK to just start an async task and not await it or even remember its Promise, for instance if you don't care about its return value and just want it to get executed "later".
Are there guidelines or best practices about when in JS/TS, either in browser or in node.js, it's acceptable to just let an async task go, and not keep a reference to it? Clearly if you care about its return value in the mainline code you have to await it, and similarly if you care that errors are reported before the main function completes. What are other cases and considerations?
To be clear I'm not asking about opinions, I'm asking what are the important things to keep in mind when "firing and forgetting" async tasks (if that's the right term).
Whether and when to do this will be a matter of opinion, so that part is off-topic for Stack Overflow.
But the concrete part is the question of: if you do this, are there any precautions you have to take? And the answer there is: Yes. You need to catch and handle errors.
Consider this fire-and-forget async call that returns a promise:
doSomethingAsync();
If the thing being done can fail, that promise can be rejected. If it's rejected and nothing handles that rejection, then:
In a browser, you get an error written to the browser console. 99.9999999% of users won't notice.
In Node.js, you get this error written to the console:
(node:26477) [DEP0018] 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.
Note that warning: At some point, a future version of Node.js may start *terminating the process when you allow a promise rejection to go unhandled.
So if you fire-and-forget, be sure to catch errors, even if you just silently swallow them:
doSomethingAsync().catch(error => {}); // Obviously only do this if you really don't
// care about the error!
Fun side note: In JavaScript, you have unhandled promise fulfillment all the time. Consider:
doSomethingAsync()
.then(result => {
// ...do something with result...
})
.catch(error => {
// ...handle/report the error...
});
Are all the promises handled there?
No. And that's just fine.
Remember that then and catch create and return a promise. The promise returned by catch in the above has no handlers attached to it. That's fine, provided it is only ever fulfilled, not rejected. Nothing ever does anything with that fulfillment. :-)
I think the general concern I would have is, do you have a mechanism to handle errors?
eslint has a no-floating-promises rule that at the very least forces you to add a .catch(), which I think is good.
I am jumping onto a node.js project where some errors fail silently and the app doesn't throw any errors unless I put it in a try..catch block. For example:
console.log(ok) // doesn't throw any errors during runtime; the api just fails silently.
try {
console.log(ok)
} catch(e) {
console.log(e)// throws ReferenceError: ok is not defined
}
Does anyone know why this might be?
Thank you in advance!
Does anyone know why this might be?
Clearly, code in a containing scope is catching and suppressing errors. Which is an unfortunate, but common, practice.
Sadly you'll just have to root out that code, there's no real shortcut. You could run with --inspect (on recent versions of Node) and tell the debugger to stop on all exceptions, even caught ones, which may help you track them down some. But it's going to be tedious, I'm afraid.
One possible further thought: On only-slightly-older versions of Node, unhandled Promise rejections are not reported. So if that code is inside (say) a Promise executor (the function you pass new Promise) or a then or catch handler (or an async function), the throw is being converted to a promise rejection. The latest versions of Node report unhandled Promise rejections. (In the not-distant-future, they'll also terminate the Node process.)
In the situation that we do not deal with reject, must we include reject in the promise executor?
for example:
new promise ((res)=>{
res(a);
})
If you know that a promise will never be rejected, adding a rejection handler is not necessary. E.g. it's obvious this will never be rejected and adding a rejection handler would be plain silly:
Promise.resolve("abc")
.then(result => console.log(result));
In all other cases, not providing a rejection handler is pretty much the same as not wrapping an error-throwing piece of code in try-catch. It might be intentional, of course, but bear in mind that node.js will treat unhandled promise rejections as if they were uncaught errors: by terminating the node.js process. That's what it says on the console when an unhandled promise rejection occurs:
(node:7741) 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.
So, summing up:
skip rejection handlers when you know for a fact a rejection is impossible,
skip rejection handlers when you require the application be terminated upon rejection (in future versions of Node.js),
write rejection handlers in all other cases.
EDIT (the question became clearer after the edits were made, hence my update to the answer):
As for including the reject() call in the promise executor, no, you do not need to add it. Again though, you have to make sure that the internal logic guarantees that resolve() will always be called.
It's not necessary, no, but it's not a very elegant way to handle errors - if an error is thrown whilst your promise is pending, you'll want to take some other action and reject the promise, letting the user know what's wrong.
Have a look at MDN Promise Docs, they explain the concept of handling errors in Promises pretty well!
The word "possibly" suggests there are some circumstances where you can get this warning in the console even if you catch the error yourself.
What are those circumstances?
This is pretty well explained in the docs:
Unhandled rejections/exceptions don't really have a good agreed-on
asynchronous correspondence. The problem is that it is impossible to
predict the future and know if a rejected promise will eventually be
handled.
The [approach that bluebird takes to solve this problem], is to call a
registered handler if a rejection is unhandled by the start of a
second turn. The default handler is to write the stack trace to
stderr or console.error in browsers. This is close to what happens
with synchronous code - your code doesn't work as expected and you
open console and see a stack trace. Nice.
Of course this is not perfect, if your code for some reason needs to
swoop in and attach error handler to some promise after the promise
has been hanging around a while then you will see annoying messages.
So, for example, this might warn of an unhandled error even though it will get handled pretty well:
var prom = Promise.reject("error");
setTimeout(function() {
prom.catch(function(err) {
console.log(err, "got handled");
});
}, 500);