puppeteer event error handler don't go to catch block - javascript

I'm creating a web scraping with puppeteer(nodejs), in some specific part of the web scraping i'm listening an event that is fired when the browser launch an dialog box with a message. on when this event is launched, i throw an exception, but this exception make the main process exit, and doesn't go to catch(err)
Example:
let page = null
const login = async () => {
//Event listening
page.on('dialog', async dialog => {
throw new Error('login_error')
}
async function processWebScraping () {
try{
page = // Initialize puppeteer page
await login()
[...]
} catch(e){
// the Error doen't come here, i got an uncaughtException
console.log(e)
}
someone know how can I get this error and handdle it?

Try wrapping your listener in a Promise instead. Doing so will allow to call .then() to resolve it and .catch() to catch an error. In your example it will look like this
const login = () => new Promise((resolve, reject) => {
page.on('dialog', dialog => {
reject(new Error('login_error'));
});
});
function processWebScraping() {
// page initialization
login().then(() => {
console.log('No error here');
}).catch(err => {
// Handle the error
console.error(err);
});
}

Related

Why does this Promise time out?

I'm working on a Telegram bot. I am trying to create a way to wait for a user response using the promises, but the promise I created to accomplish this hangs and times out.
The code for the Telegram command handler sends a few messages and registers a handler function for the reply event:
export default async function (ctx: BacchusContext): Promise<void> {
if(!ctx.isAdmin){
await ctx.reply('You are not a recognized administrator. You can\'t use this command.');
return;
}
await ctx.reply('Alright, so we\'re adding a party. Cool. When is it?');
const rep = await new Promise((resolve, reject) => {
ctx.onReply( resolve);
});
await ctx.reply(`On Reply: ${rep}`);
}
The registration of the reply event handler is shown below. The function passed to onReply is saved in a session object independent of the telegram context (a static class member):
private static middleware_reply() {
return (ctx: BacchusContext, next) => {
ctx.onReply = (fcn: (rCtx) => void | Promise<void>) => {
ctx.setSessionData<(replyCtx) => void | Promise<void>>('onReply', fcn);
};
ctx.waitForReply = () => {
return new Promise<string>(resolve => {
ctx.setSessionData<(replyCtx) => void | Promise<void>>('onReply', (replyCtx) => {
return resolve(replyCtx.message.text);
});
});
};
return next();
};
}
As soon as the user sends a text message to the bot, the code checks if a onReply handler was defined for the current session, and if it was it will be executed and then removed. The event handler is only supposed to fire once.
this.bot.on('text', (ctx: BacchusContext, next) => {
const handler = ctx.getSessionData<(replyCtx) => Promise<void>>('onReply');
if(handler){
debug('Executing reply function');
handler(ctx);
// Delete handler
ctx.setSessionData<(replyCtx) => Promise<void>>('onReply', null);
}
return next();
});
The code so far makes sense to me, but I keep getting the following error:
ERROR [unhandledRejection] Promise timed out after 90000 milliseconds 14:38:55
at Timeout._onTimeout (node_modules\p-timeout\index.js:39:64)
at listOnTimeout (node:internal/timers:559:17)
at processTimers (node:internal/timers:502:7)
Why does this promise time out?
EDIT:
What's weird about this code is that the following code works fine:
ctx.onReply(async (replyCtx) => {
await ctx.reply(`On Reply: ${replyCtx.message.text}`);
});
So the callback function works fine, but as soon as it is 'promisified' it stops working. Why could that be?

Return from a function when one of try ... catch block throws an error

I've got a function where I submit a large form and it makes multiple API calls. I tried to separate it into smaller functions cause there is some additional logic that depends on API response.
I wrapped each API call within a function in try ... catch so that I have more control over the errors. The problem is that I need to terminate the parent function whenever one of the child function throws an error and I can't figure out the clean way of doing that.
So the code is the following:
const func1 = async() => {
try {
// api call + logic
} catch (error) {
// show error toast and terminate formSubmit function
}
}
const func2 = async() => {
try {
// api call + logic
} catch (error) {
// show error toast and terminate formSubmit function
}
}
const func3 = async() => {
try {
// api call + logic
} catch (error) {
// show error toast and terminate formSubmit function
}
}
const formSubmit = async () => {
await func1()
await func2()
await func3()
}
Just throw a new error or rethrow the error and then add a new handler in your formSubmit function.
const func1 = async() => {
try {
// api call + logic
} catch (error) {
// show error toast and terminate formSubmit function
throw new Error("...");
}
}
const func2 = async() => {
try {
// api call + logic
} catch (error) {
// show error toast and terminate formSubmit function
throw new Error("...");
}
}
const func3 = async() => {
try {
// api call + logic
} catch (error) {
// show error toast and terminate formSubmit function
throw new Error("...");
}
}
const formSubmit = async () => {
try {
await func1()
await func2()
await func3()
} catch(e){
// do what needs to be done on error
}
}

Catching async errors with context referenced to originating sync function

In the code below the error is never caught:
const fn = () => {
try {
setTimeout(() => { throw new Error('An exception is raised') }, 10)
} catch (error) {
console.error({ error })
}
}
fn()
The following is a solution:
const fn2 = () => {
const errorFn = (error) => console.error({ error })
setTimeout(() => {
try {
throw new Error('An exception is raised')
} catch (e) { errorFn(e) }
}, 10)
}
fn2()
The downside to this solutions is that it has to be implement in the function within setTimeout. That's fine if one controls the code, but if users are supplying the code and calling setTimeout and don't implement appropriate error handling, it could bring down one's server!
Another solution is process.on('uncaughtException,... but that loses the context of the originating sync call that initiated the async function. Unless there is some clever way to supply that context?
Are there any other ways to catch async errors with context to the originating sync code?
Could one set a default error handler for a particular async branch - that catches all unhandled errors that may occur in that async branch?

Why am I unable to catch discord.js promise rejections in event callbacks?

So I'm making a discord bot. For simplicity's sake, here is a very small portion that illustrates my problem:
const Discord = require('discord.js');
const client = new Discord.Client();
client.on('ready', async () => {
throw new Error('Omg');
});
async function start() {
try {
await client.login(process.env.DISCORD_BOT_TOKEN);
} catch (err) {
console.error('Caught the promise rejections');
}
}
start();
When I run this code, I expect the output to be Caught the promise rejections and the process should subsequently exit. However this is not the case. Instead I get a PromiseRejectionWarning and the process does not exit (I have to press Ctrl-C to do so). I first thought that maybe errors in callbacks don't get propagated to code that calls them, so I made another pure JS example:
const client = {
on(event, callback) {
this.callback = callback;
},
async login(token) {
while (true) {
// I assume the login method calls the callback in D.js
// (where else could it be called?)
await this.callback();
await sleep(5000);
}
},
};
client.on('ready', async () => {
throw new Error('Omg');
});
async function start() {
try {
await client.login(process.env.DISCORD_BOT_TOKEN);
} catch (err) {
console.error('Caught the promise rejections');
}
}
start();
However in this case, the output is exactly as expected; I see the line from the catch and the process immediately exits. Without the catch I get the unhandled promise rejection errors and an unfinished process.
So my question: Why am I unable to catch promise rejections in my event callbacks (like on('ready'))?
The reason is, because your second code, is not how discord event emitter works, nor Node.js built in EventEmiter does.
The callback function for the ready event is not executed with an await, and it doesn't have a .catch handler attached to it, that's why you get an UnhandledPromiseRejectionWarning.
When using async in an EventEmitter callback, you should handle the error, if you don't you'll get the warning, because no other code is handling it.
client.on('ready', async () => {
try {
throw new Error('Omg');
} catch(e) {
}
});
In your specific case, it seems that you want to trigger an error if some condition is met on 'ready'. So what you should do instead, is wrap that listener in a Promise.
function discordReady(client) {
return new Promise((resolve, reject) => {
client.once('ready', async () => {
reject(new Error('Omg'));
// resolve..
});
})
}
async function start() {
try {
await Promise.all([
discordReady(client),
client.login(process.env.DISCORD_BOT_TOKEN),
]);
} catch (err) {
console.error('Caught the promise rejections');
}
}
That will get you the expected behaviour

Handling network errors with axios and Twilio

I have an application that uses axios for it's ajax requests. When a user experiences a network issue (for example, their wifi goes out and they no longer have an internet connection while on my application), I want to make sure that only the first axios request is made, and if I detect there is a network issue, to not attempt any more requests, but instead to retry the same request until successful.
My application performs many requests, including a request every 2.5 seconds (in this example, getData). It also establishes a Twilio connection when the application initializes (it executes twilio() on initialization).
When a connection is lost, the following happens:
getData fails, resulting in a console message of this is a network error.
TwilioDevice.offline is executed. This results in two error messages: first a this is a network error. message (error message #2) when TwilioDevice.offline tries fetchToken(), and then a received an error. message (error message #3) after the fetchToken() fails.
Given #'s 1 and 2, how can I make sure that:
If I experience a network error, I only receive one error message instead of 3 saying that "there was a network error"
My app detects that there is a network error, then tries to re-establish a connection, then, if successful, resumes fetching data, Twilio tokens, etc.
Thanks! Code is below.
example code:
const getData = async () => {
try {
const response = await axios.get('api/data');
return response.data;
} catch (error) {
handleError(error);
}
};
const fetchToken = async () => {
try {
const data = await axios.get('api/twilio-token');
return data.token;
} catch (error) {
return handleError(error);
}
};
const handleError = error => {
if (!error.response) {
console.log("this is a network error.");
} else {
console.log("received an error.");
}
};
twilio.js:
import { Device as TwilioDevice } from 'twilio-client';
const registerEvents = () => {
TwilioDevice.ready(() => {
console.log('Twilio.Device is now ready for connections');
});
TwilioDevice.connect((conn) => {
console.log(`Connecting call with id ${conn.parameters.CallSid}`);
// code to start call
conn.disconnect((connection) => {
console.log(`Disconnecting call with id ${connection.parameters.CallSid}`);
// code to end call
});
});
TwilioDevice.error((error) => {
console.log("Twilio Error:");
console.log(error);
});
TwilioDevice.offline(async () => {
try {
const newTwilioToken = await fetchToken(); // error message #2
return TwilioDevice.setup(newTwilioToken);
} catch (error) {
return handleError(error); // error message #3
}
});
};
export const twilio = async () => {
try {
registerEvents();
const twilioToken = await fetchToken();
TwilioDevice.setup(twilioToken);
} catch (error) {
return handleError(error);
}
};
I would recommend making your fetchToken and getData methods to throw errors rather than handling it themselves so that they can be handled by their outer functions.
Something like,
const getData = async () => {
try {
const response = await axios.get('api/data');
return response.data;
} catch (error) {
throw (error);
}
};
const fetchToken = async () => {
try {
const data = await axios.get('api/twilio-token');
return data.token;
} catch (error) {
throw (error);
}
};
So that when you call twilio() that function can handle the error like retrying etc.

Categories

Resources