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?
Related
I'm making a discord bot using discord.js that listens to slash commands in a discord server and manipulates data on a google sheet. I've come across a frustrating problem while trying to make a confirmation system for a command. The issue is with the getConfirmation() method. Here is the relevant code:
async execute(interaction) {
try {
await interaction.deferReply();
// get player from spreadsheet
const iss = interaction.options.getInteger("iss");
const player = await getPlayerByISS(iss);
// get confirmation
let hasConfirmed = await getConfirmation(interaction, player);
if (!hasConfirmed) {
await interaction.editReply("Draft choice abandoned.");
return;
}
// if confirmed, draft the player
let draftedPlayer = await draftPlayer(interaction.user.tag, player);
await interaction.editReply(draftedPlayer);
} catch (error) {
console.log(error);
await interaction.editReply("Could not draft player");
}
}
and
async function getConfirmation(interaction, player) {
try {
// send confirmation msg
const message = await interaction.channel.send({
content: `Are you sure you want to draft ${formatPlayerString(
player
)}?`
});
const filter = (reaction, user) => {
return (
["👎", "👍"].includes(reaction.emoji.name) &&
user.id === interaction.user.id
);
};
// react to the msg to allow for easier reacting
await Promise.all([message.react("👍"), message.react("👎")]);
// awaitReactions just returns a list of all reaction objs that match above
// filter
let didConfirm, responseMessage;
message
.awaitReactions({
filter,
max: 1,
time: 60000,
errors: ["time"],
})
.then((collected) => {
const reaction = collected.first();
didConfirm = reaction.emoji.name === "👍";
responseMessage = didConfirm
? "Confirmed. Drafting now..."
: "Cancelled confirmation.";
})
.catch((collected) => {
console.log(collected);
responseMessage =
"Did not receive response in time. Abandoning...";
didConfirm = false;
})
.finally(async (collected) => {
// clean up after 3s to prevent clutter
setTimeout(() => message.delete(), 3000);
// resolve the promise
await message.edit(responseMessage);
return didConfirm;
});
} catch (error) {
console.log("Errored out : ", JSON.stringify(error));
console.log(error);
throw error;
}
}
Sorry for the large chunk of code but let me explain. I want the bot to send a confirmation message, listen for any reaction replies to the message, and then proceed with drafting the player if the user who sent the /draft command reacts with a thumbs up emoji, and abandons otherwise.
The issue is with the getConfirmation() method. Despite me having designated getConfirmation() as an async method, which should wrap my response in a promise and awaiting it, the code in execute() continues to run immediately after the await getConfirmation() line. It sets hasConfirmed = undefined and proceeds with that assumption. So no matter what the user reacts, the code will reach the !hasConfirmed block, and respond with "Draft choice abandoned".
However, this does not make any sense to me. Since getConfirmation() is an async method, and we are awaiting it in another async method, shouldn't the await keyword halt execution until the promise has been fulfilled and hasConfirmed has a value? The confirmation message sent in getConfirmation does get correctly updated when the user reacts though, so I know that that code is being reached. Am I missing something obvious? Do I just fundamentally misunderstand asynchronous programming?
I'd really appreciate any help! Thanks a lot.
Edit: Phil's answer was it
getConfirmation() doesn't return anything so will always resolve to undefined. FYI the return value from finally() is not used to resolve the promise chain
// Quick demo showing what happens with finally
Promise.resolve(undefined).finally(() => "finally").then(console.log);
It's also best not to mix async / await with .then() / .catch() as it can quickly get confusing.
I would instead structure the await reactions part of your code like this
let didConfirm, responseMessage;
try {
const collected = await message.awaitReactions({
filter,
max: 1,
time: 60000,
errors: ["time"],
});
const reaction = collected.first();
didConfirm = reaction.emoji.name === "👍";
responseMessage = didConfirm
? "Confirmed. Drafting now..."
: "Cancelled confirmation.";
} catch (err) {
console.warn(err);
responseMessage = "Did not receive response in time. Abandoning...";
didConfirm = false;
}
// Things after an async try..catch is similar to finally
// only you can actually return a value
// clean up after 3s to prevent clutter
setTimeout(() => message.delete(), 3000);
// resolve the promise
await message.edit(responseMessage);
return didConfirm;
I'm trying to turn off unhandled promise rejection on my discord.js bot that uses an event handler, I've found this online on how to do it, but it doesn't seem to work with my event handler: solution I've found online.
Here's my event handler:
fs.readdir('./events/', (err, files) => {
files.forEach(file => {
const eventHandler = require(`./events/${file}`)
const eventName = file.split('.')[0]
client.on(eventName, arg => eventHandler(client, arg));
})
})
My event files look something like this:
const Discord = require('discord.js');
module.exports = (client) => {
//code
}
How would I implement the code in the picture into my event handler?
Right now, you aren't handling errors thrown by the discord.js library. You have a handler like:
module.exports = (client) => {
//code
}
That means that if the discord.js code throws an error, there is no code to handle the error.
You can use a few techniques to solve the problem. If you're a fan of async/await, you can change your handler to be an async function and use try/catch to handle the errors:
module.exports = async (client) => {
try {
// call discord.js code
} catch (error) {
// handle rejected promise from discord.js code
}
}
You can also use the non-async/await style of handling errors from chains of promises:
module.exports = async (client) => {
client.somePromiseIsReturned()
.then(result => {
// handle result of discord.js operation
})
.catch(error => {
// handle rejected promise from discord.js code
};
}
The technique the solution you posted uses is to handle all errors the same way, by using console.log to log the error's message. Their code is a concise way of doing that:
somePromiseIsReturned().catch(console.log);
is equivalent to:
somePromiseIsReturned().catch(error => console.log(error));
I have a Node.js Lambda function that during the inital run ran fine, however during the subsequent runs - 1 minute interval - it is completing in about 1.5ms and not running my code at all other than outputting finished - the console.log in my then block.
What would the reason for this be?
module.exports.perf = function(event, context, callback) {
context.callbackWaitsForEmptyEventLoop = false;
let input = JSON.parse(event.Records[0].body);
const point1 = new Point('elapsedTime')
.tag(input.monitorID, 'monitorID')
.floatField('elapsedTime', input.perf_value)
writeApi.writePoint(point1)
writeApi
.close()
.then(() => {
console.log('FINISHED ... ')
})
.catch(e => {
console.error(e)
if (e instanceof HttpError && e.statusCode === 401) {
console.log('Run ./onboarding.js to setup a new InfluxDB database.')
}
console.log('\nFinished ERROR')
})
return
};
EDIT**
const writeApi = new InfluxDB({url: InfluxURL, token}).getWriteApi(org, bucket, 'ms')```
I ended up coming up with a solution thanks to someone elsewhere online and the result was changing .close() to .flush() in the following code
writeApi
.flush() # used to be .close()
.then(() => {
console.log('FINISHED ... ')
})
The actual Problem
The problem I am trying to solve is that a user may not provide enough information on a message (discord). To then get all of the necessary data the user is prompted to react to a bot message.
There are 3 stages of that bot message, "InitialPrompt", "TimeoutWarning" and "TimeoutSuccess(TicketFailure)".
What I wanted the solution to be
With the way I've written my code there is no way to abort the timeout after it has been initialized. I thought that throwing an error would stop the execution of a function. I guess that doesn't happen because async calls get queued up instead of being ran line by line.
Is there a way to do this without adding a boolean and checking infront of each function call?
The solution that I could come up with
const interPtr = {interrupted : false};
interruptSignal(interPtr);
if(interPtr.interrupted) return;
console.log("...");
...
The actual code
JS Fiddle
(async () => {
const sleep = async time => new Promise(resolve => setTimeout(resolve, time));
const interruptSignal = () => new Promise(async (res, rej) => {
console.log("interruptStarted")
await sleep(2000);
console.log("interruptRan");
throw "Interrupted"
});
const timeOutHandler = async () => {
interruptSignal();
console.log("TimeoutStarted")
await sleep(5000);
console.log("TimeoutWarning")
await sleep(5000);
console.log("TimeoutSuccess->TicketFailure")
};
try {
await timeOutHandler();
} catch (e) {
console.log(e);
}
})()
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);
});
}