UnhandledPromiseRejectionWarning followed by PromiseRejectionHandledWarning - node.js

The following code gives an UnhandledPromiseRejectionWarning for p2, despite errors on p2 being explicitly handled.
function syncFunctionReturnsPromise(val)
{
return new Promise((r,x)=> {
setTimeout(()=> {
if (val) {
r('ok');
} else {
x('not ok');
}
}, val?1000:500);
});
}
async function test(){
let p1 = syncFunctionReturnsPromise(true);
let p2 = syncFunctionReturnsPromise(false);
await p1.catch(ex => console.warn('warning:', ex)); //errors in these 2 promises
await p2.catch(ex => console.warn('warning:', ex)); //are not the end of the world
console.log('doOtherStuff');
}
test();
The output looks like this:
(node:9056) UnhandledPromiseRejectionWarning: not ok
(node:9056) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:9056) [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.
warning: not ok
doOtherStuff
(node:9056) PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1)
It is not immediately obvious to me why this should be

So this is because the first await waits synchronously before the handler is attached to p2. But p2 fails before p1 completes.
So node detects that p2 failed without any error handling.
N.B. In later versions of node this may cause the program to terminate, rather than just a warning.
The fix is to attach the handlers before awaiting
async function test(){
let p1 = syncFunctionReturnsPromise(true);
let p2 = syncFunctionReturnsPromise(false);
p1 = p1.catch(ex => console.warn('warning:', ex)); //errors in these 2 promises
p2 = p2.catch(ex => console.warn('warning:', ex)); //are not the end of the world
await p1;
await p2;
console.log('doOtherStuff');
}
Obviously you could inline that on the declaration although in my realworld code its neater as separate lines.

Here is the other way:
async function test(){
let p1 = await syncFunctionReturnsPromise(true)
.catch(ex => console.warn('warning:', ex));
let p2 = await syncFunctionReturnsPromise(false)
.catch(ex => console.warn('warning:', ex));
console.log('doOtherStuff');
}

Related

Node logging unexpected UnhandledPromiseRejectionWarning

I have a piece of code that's causing Node to log UnhandledPromiseRejectionWarning. But I'm not sure why. Here's the code boiled down:
export class Hello {
async good(): Promise<string> {
let errorP = this.throwError();
let responseP = this.doSomething();
let [error, response] = await Promise.all([errorP, responseP]);
return response + '123';
}
async bad(): Promise<string> {
let errorP = this.throwError();
let responseP = this.doSomething();
let response = (await responseP) + '123';
let error = await errorP;
return response;
}
private async throwError(): Promise<string> {
await (new Promise(resolve => setTimeout(resolve, 1000)));
throw new Error('error');
}
private async doSomething(): Promise<string> {
await (new Promise(resolve => setTimeout(resolve, 1000)));
return 'something';
}
}
Calling try { await hello.bad(); } catch (err) {} causes node to log UnhandledPromiseRejectionWarning
Calling try { await hello.good(); } catch (err) {} does NOT log the warning
Full error:
(node:25960) UnhandledPromiseRejectionWarning: Error: error
at Hello.<anonymous> (C:\hello-service.ts:19:11)
at Generator.next (<anonymous>)
at fulfilled (C:\hello-service.ts:5:58)
at runNextTicks (internal/process/task_queues.js:58:5)
at listOnTimeout (internal/timers.js:523:9)
at processTimers (internal/timers.js:497:7)
(node:25960) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch()
. To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)
(node:25960) [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.
{"level":30,"time":1639745087914,"pid":25960,"hostname":"AZWP10801-12","reqId":"req-1","dd":{"trace_id":"2081604231398834164","span_id":"2081604231398834164","service":"#amerisave/example-service","version":"0.0.0"},"res":{"statu
sCode":200},"responseTime":1025.4359999895096,"msg":"request completed"}
(node:25960) PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1)
Some dependency versions:
node ver. 14.16.1
ts-node-dev ver. 1.1.8
ts-node ver. 9.1.1
typescript ver. 4.5.2
Why is good good, but bad bad?
The problem in bad() is because the errorP promise rejects BEFORE you get to await errorP and thus it rejects when there is no reject handler for it in place. Nodejs detects that a promise rejected and your process gets back to the event loop and that rejected promise does not have a reject handler on it. That gets the "unhandled rejection" warning.
Notice here that while await errorP doesn't directly apply a reject handler, it does tie errorP to the parent async function which does have a reject handler on it, so the await errorP indirectly assigns reject handling to errorP. Whereas errorP by itself will just reject and not cause anything to happen to the parent async function. It will just be a variable containing a now rejected promise with no reject handler on it.
To take advantage of async automatic error propagation of rejected promises, you have to await that promise.
Nodejs doesn't know you're going to add the await in the future with code that will execute some time in the future, so it will report the unhandled rejection.
Code becomes subject to these types of errors where you put a promise into a variable and you have no reject handler on that promise of any kind and then you go await on some other promise BEFORE you ever put any sort of reject handler on that previous promise. The promise is just sitting there with no error handling on it. If, due to the timing of things, it happens to reject in that state, you will get the warning. The usual solutions are:
Immediately put error handling on the promise so it's never left sitting by itself.
Don't create the promise until you're ready to use it however you're going to use it with appropriate error handling (with a .then().catch() or in a Promise.all().catch() or in an await or whatever).
Don't await other promises while a promise is sitting in a variable without any reject handling.
I find that if I can avoid putting a promise with no handling on it into a variable at all and rather just create the promise right into the circumstance where it's going to be monitored for completion and error, you don't even have to generally think about this issue.
FYI, you can illustrate the same general concept of a promise rejecting before you add a reject handler in a simpler manner here if you run this in nodejs:
function bad(t) {
return new Promise((resolve, reject) => {
setTimeout(reject, t);
});
}
const b = bad(500);
// this timer will fire after bad() rejects
setTimeout(() => {
b.catch(err => {
console.log("caught b rejection");
})
}, 600);
You will get the "uncaught rejection" error because when the promise rejects, it does not yet have a .catch() handler. Your code has this same issue (though obscured a little more) because the reject handler comes from the await and the async function and the try/catch the caller of the async function is using.
Here's a hypothesis (that can be experimentally proven).
The difference in behavior between good and bad can be explained by the order of awaits.
In bad you're awaiting on throwError after you have awaited on doSomething, while in good, you're awaiting on Promise.all, which will not return until both are fullfilled or at least one is rejected (which will be the case here).
So in bad, the throwing is happening outside of await, and your catch is not triggered, and it is caught internally by node.
If you change your bad so that you await on throwError first, then your catch will get triggered:
async bad(): Promise<string> {
let errorP = this.throwError();
let responseP = this.doSomething();
let error = await errorP;
let response = (await responseP) + '123';
return response;
}

Explicit wait for a selector isn't working?

I'm writing a code to log in Gmail. On the password page, instead of using implicit wait, I want to use explicit wait instead. However, it is not picking up my selector?
(async () => {
const browser = await puppeteer.launch({
headless: false
});
const page = await browser.newPage();
await page.goto('https://accounts.google.com/');
await page.$('#identifierId');
await page.keyboard.type('Test1234');
await page.click('#identifierNext > content > span');
await page.waitForSelector('#password'); //this doesnt work
// await page.waitFor(5000); this works
await page.$('#password > div.aCsJod.oJeWuf > div > div.Xb9hP > input');
await page.keyboard.type('fakePassword');
await page.click('#passwordNext > content');
);
I'm getting the error:
(node:14428) UnhandledPromiseRejectionWarning: Error: Node is either not visible or not an HTMLElement
at ElementHandle._clickablePoint (/Users/asd/Projects/FreeRazor/node_modules/puppeteer/lib/JSHandle.js:199:13)
at processTicksAndRejections (internal/process/next_tick.js:81:5)
-- ASYNC --
at ElementHandle. (/Users/asd/Projects/FreeRazor/node_modules/puppeteer/lib/helper.js:110:27)
at DOMWorld.click (/Users/asd/Projects/FreeRazor/node_modules/puppeteer/lib/DOMWorld.js:367:18)
at processTicksAndRejections (internal/process/next_tick.js:81:5)
-- ASYNC --
at Frame. (/Users/asd/Projects/FreeRazor/node_modules/puppeteer/lib/helper.js:110:27)
at Page.click (/Users/asd/Projects/FreeRazor/node_modules/puppeteer/lib/Page.js:988:29)
at /Users/asd/Projects/FreeRazor/app.js:19:16
at processTicksAndRejections (internal/process/next_tick.js:81:5)
(node:14428) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:14428) [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.
The page.waitForSelector statement is working. One of the page.click calls is the problem.
Relevant part of the error message:
(node:14428) UnhandledPromiseRejectionWarning: Error: Node is either not visible or not an HTMLElement
[...]
at Page.click (/Users/asd/Projects/FreeRazor/node_modules/puppeteer/lib/Page.js:988:29)
at /Users/asd/Projects/FreeRazor/app.js:19:16
So the error happens in line 19. I don't know for sure which line it is, but I'm assuming it is the latter page.click call as you are saying the code works if you wait longer (page.waitFor(5000)). So it seems it takes the page longer to display the #passwordNext > content DOM element than the #password element.
Solution
You can solve this problem by putting another waitForSelector before your click to make sure the element actually exists. I even added the option { visible: true } to make sure the DOM node is also visible:
await page.waitForSelector('#passwordNext > content', { visible: true });
await page.click('#passwordNext > content');

Promises: is asynchronous handling OK?

My question: Is it safe to detach the handling from its Promise object?
If I do this ...
var promise1 = new Promise(function(resolve, reject) {
var json = { "counter":0 }
console.log('execute now, worry later ...')
json.counter++;
console.log(json.counter)
resolve(json);
});
var then = function() {
promise1.then(function(value) { console.log('value: '+value.counter) });
}
setTimeout(then, 3000);
var promise2 = new Promise(function(resolve, reject) {
console.log('error thrown here ...')
throw new Error('will it behave the same as with then?');
});
var catchFunc = function() {
promise2.then().catch(function(error) { console.log('error: '+error.message) });
}
setTimeout(catchFunc, 3000);
Then I get warnings that make sense ...
execute now, worry later ...
1
error thrown here ...
(node:9748) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: will it behave the same as with then?
(node:9748) PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1)
value: 1
error: will it behave the same as with then?
Background: In some cases, I want my promises to run concurrently. In some cases, I would like them to be run sequentially. The simplest implementation I've found is to wrap both variants into a function, push it in a map, and treat the "then / catch" in a reduce. The result is that my handling in the first variant (concurrently) is at the time of the wrapping and detached as in my sample above.
If it is safe, how can I remove the warnings from my log?
Rejected promise should be chained with catch on same tick. If this doesn't happen, UnhandledPromiseRejectionWarning appears. It's expected to become an exception in next Node versions, so it's should be avoided.
Once promise control flow was introduced, it's beneficial to use promises. setTimeout stands out and doesn't provide error handling for promises.
If promises are processed concurrently, they are usually handled with Promise.all(...).catch(...). In this case resulting promise is chained with catch on same tick (a similar problem is addressed in this answer).

Possible unhandled promise rejection (id:0) null is not an object

So I have been working on a react native application just like the meetups app.
It has its own node.js backend which can be viewed here
https://github.com/rahullakhaney/meetup/tree/master/meetup-backend
While in my application, I am trying to populate the groups from my database, I get this error "Possible unhandled promise rejection (id:0) null is not an object"
Here is my api.js file
import axios from 'axios';
axios.defaults.baseURL = 'http://localhost:3000/api';
const fakeGroupId = '58d64e3a122149dd3cdba5d8';
class MeetupApi {
constructor() {
this.groupId = fakeGroupId;
this.path = `/groups/${this.groupId}/meetups`;
}
async fetchGroupMeetups() {
const { data } = await axios.get(this.path);
return data.meetups;
}
}
export {
MeetupApi
};
You can also view the complete code at https://github.com/rahullakhaney/meetup/tree/master/meetup-mobile
Can anyone please explain why am I getting this error, sorry but I am new to react native.
Every function or method declared with the async keyword returns a promise. That promise is resolved when you return something from that function and it is rejected when you throw an exception in that function.
When you write this:
const { data } = await axios.get(this.path);
then what really happens is that you add a resolve callback to the promise returned by axios.get() but every rejection of that promise returned by axios.get() is raised as an exception inside of the fetchGroupMeetups() method. You don't use try/catch so that exception propagates and is in turn converted into a rejection of the promise that is returned by fetchGroupMeetups() - which you probably don't handle.
To handle that rejection you either need to use it as something like:
x.fetchGroupMeetups(...).catch(err => console.log('Error:', err));
or, inside of other async function:
try {
x.fetchGroupMeetups(...);
} catch (err) {
console.log('Error:', err);
}
but of course doing something more than just printing the error.
To know more details on what are those unhandled rejections and why you should always handle them, see this answer:
Should I refrain from handling Promise rejection asynchronously?
TL;DR: The unhandled rejections used to be warnings but will now crash your app. Here is why.

unhandled rejections holding references

I have a doubt while running below JavaScript in node.js.
var z = new Image();
function x()
{
var promise = new Promise();
return promise;
}
var promise = x();
promise.then(function(){});
..........
promise.reject(z);
There is no reject handler added to promise returned by x(). But, at some point if we are sending reject with response value z, whether z will be garbage collected or will be still held due to unhandled rejection having reference to it. But, when I add catch/reject handler to promise, I am seeing garbage collection happens for z.
Please clarify why garbage collection not happening for Z, when passed to unhandled rejection.
That's not how you use [native A+] promises.
Try like this:
function x()
{
return new Promise((resolve,reject) => {
if(success()) {
resolve(success_val);
} else {
reject(new Error("fail!"));
}
});
}
let promise = x();
promise.then(function(){});
You can't reject a promise outside of where it's created. You have to use the (resolve,reject) constructor callback.

Resources