Node.js asynchronous and synchronous integration - node.js

In short, I am making a Discord bot with discord.js and I am having trouble with asynchronous and synchronous functions.
In order to assign variables, the last part of the function loops through each variable and then converts it to its desired type, which can be seen here:
argsList.forEach((argument, index) => {
let finalArgument = argument
const type = args[index].type
if (type === UserArgument) {
new UserArgument(argument).result
.then(userObject => {
finalArgument = userObject
})
.catch(error => {
throw error
})
} else if (type === MemberArgument) {
new MemberArgument(argument, guild).result
.then(memberObject => {
finalArgument = memberObject
})
.catch(error => {
throw error
})
} else if (type === ChannelArgument) {
new ChannelArgument(argument, guild).result
.then(channelObject => {
finalArgument = channelObject
})
.catch(error => {
throw error
})
} else if (type === RoleArgument) {
new RoleArgument(argument, guild).result
.then(roleObject => {
finalArgument = roleObject
})
.catch(error => {
throw error
})
}
finalArgList.push(finalArgument)
})
return finalArgList
}
And here is an example of how the UserArgument class looks like (all other argument bases basically look the same)
class UserArgument {
constructor(user) {
this.result = new Promise((resolve, reject) => {
if (typeof(user) === "string") {
if (user.match(/^<#!([0-9]+)>$/)) {
user = user.match(/[0-9]+/)
}
if (isNumeric(user)) {
ArgumentBase.client.users.fetch(user)
.then(userObject => {
resolve(userObject)
return userObject
})
}
this.#getUserFromName(user)
.then(userObject => {
resolve(userObject)
return userObject
})
} else if (user instanceof DiscordJS.User) {
resolve(user)
return user
} else if (user instanceof DiscordJS.GuildMember || user instanceof DiscordJS.ThreadMember) {
resolve(user.user)
return user.user
}
let userObject = ArgumentBase.client.users.resolve(user)
if (userObject) {
resolve(userObject)
return userObject
}
reject(new UserNotFound(toString(user)))
})
}
async #getUserFromName(username) {
return new Promise((resolve, reject) => {
for (const user in ArgumentBase.client.users.cache) {
if (user.username === username) {
resolve(user)
return user
}
}
throw new UserNotFound(username)
})
}
}
The issue that I am coming across is that the code that handles each argument does not wait for the function to be finished and instead skips over it. This of course causes the command to be executed before the arguments are even processed. For my tests, I was testing throwing errors from the UserArgument class, and the error did get thrown, but only after the command had already executed because that is when it decided to finish.
My assumption is that since Promise is an asynchronous function, the code keeps running and does not wait for it. I tried turning the argument function to an async function and use await, but I kept getting the SyntaxError: await is only valid in async functions and the top level bodies of modules, even when the function is an async function (function declaration is static async getCommandArgs(invokedString, args, guild = undefined)). If someone could help me, that would be amazing. Thank you for your time and help.

Related

telegraf how to check if user is admin?

i have this code:
function isAdmin(idOfChat, IdOfUser, ctx) {
//function
return isAdminBoolean
}
bot.command("test", ctx => {
if (isAdmin(ctx.message.chat.id, ctx.message.from.id) == true) {
ctx.reply("Admin")
}else{
ctx.reply("._.")
}
})
how to make it work?
sorry for my bad English)))
You should re-write your method as a promise (because Telegraf queries from Telegram API, so, your isAdmin method could be like this)
function isAdmin(idOfChat, IdOfUser, ctx) {
return new Promise((resolve, reject) => {
//Get user information first
ctx.telegram.getChatMember(idOfChat, IdOfUser).then((user) => {
//Then check if user is admin (or creator)
resolve(user.status == "administrator" || user.status == "creator");
})
.catch((error) => {
//Reject if it's an error
reject(error);
});
});
}
Then, for use it into your main function, you should have to handle it like this:
bot.command("test", ctx => {
isAdmin(ctx.message.chat.id, ctx.message.from.id, ctx).then((result) => {
if (result) {
ctx.reply("Admin");
} else {
ctx.reply("._.");
}
})
.catch((error) => {
ctx.reply("An error has ocurred trying to get user rank: " + JSON.stringify(error));
});
});

What is the use of resolve in promise in nodejs

function getAuthSecrets() {
return new Promise((resolve, reject) => {
let authSecrets = NamespaceManager.getNamespace('******).get('authSecrets');
if (!authSecrets) {
sm.getSecret(authConfig.secretName).then((secret) => {
NamespaceManager.getNamespace('cls2_context').set('authSecrets', secret);
resolve(secret);
}).catch((error) => {
reject(error);
});
} else {
resolve(authSecrets);
}
});
}
Here it gets called :
if (username && password) {
getAuthSecrets().then(secrets => {
What will get returned by calling getAuthSecret?
authSecrets or secret, depending on which call to resolve gets executed.
You want to return resolve(...) to avoid writing code that tries to resolve twice.
That you are catching then immediately rejecting is a sign that your machinery is more complex than it needs to be. You can lift a value to the Promise context with Promise.resolve:
function getAuthSecrets() {
const authSecrets = NamespaceManager.getNamespace('******).get('authSecrets');
return authSecrets ? Promise.resolve(authSecrets) :
sm.getSecret(authConfig.secretName).then((secret) => {
NamespaceManager.getNamespace('cls2_context').set('authSecrets', secret);
return Promise.resolve(secret);
})
});
}

Alexa responding before data is returned

I'm new to promises, async/await, and Alexa/lambda so bear with me.
My function is returning prior to the data being returned. I had a similar issue where I was getting an error, but have since edited my function quite a bit and therefore asking a new question. I'm now no longer getting an error, but instead my data is being returned first, then the promise is executing.
I've tried re-writing the promise/function after reading many, many SO, google, and amazon developer forums. Nothing seems to be working for me.
const IntentRequest = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'IntentRequest';
},
async handle(handlerInput) {
const { requestEnvelope, serviceClientFactory, responseBuilder } = handlerInput;
let responseData, promise;
checkAuthenticationStatus(handlerInput,function(json){
console.log('waited!')
if(json.error) {
return handlerInput.responseBuilder.speak(messages.NO_ACCESS).withSimpleCard('Unauthorized Request', messages.NO_ACCESS).getResponse();
} else if(json.noerror && json.noerror.okay == 'true'){
console.log('starting to get intent data')
const url = new URL(json.noerror.okay.path);
promise = new Promise((resolve, reject) => {
console.log('start promise')
return httpsGetIntent(handlerInput, url).then((resultData) => {
console.log(resultData)
responseData = resultData;
resolve(responseData)
console.log('inside promise, no error, prior to return data')
})
}).then((result) => { console.log('result', result)})
return handlerInput.responseBuilder.speak('Test').getResponse();
}
});
console.log('response data', responseData)
let result = await promise;
return result;
},
};
Out of my many console.logs() added for debugging, they print as follows:
- 'response data'
- 'waited!'
- 'starting to get intent data'
- 'start promise'
- resultData
- 'inside promise, no error, prior to return data'
While it's not fully fleshed out (I need to address the error handling portion) I wanted to share my solution here in case anyone stumbled upon this post and needed some help.
I moved the promise outside the checkAuthentication function and returned the data when it was processed. I then chained the promise with .then() and passed it the data returned, and prompted Alexa to speak.
const IntentRequest = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'IntentRequest';
},
async handle(handlerInput) {
const { requestEnvelope, serviceClientFactory, responseBuilder } = handlerInput;
let responseData, promise;
return new Promise((resolve, reject) => {
checkAuthenticationStatus(handlerInput, async function(json) {
if (json.error) {
return handlerInput.responseBuilder.speak(messages.NO_ACCESS).withSimpleCard('Unauthorized Request', messages.NO_ACCESS).getResponse();
} else if (json.noerror && json.noerror.okay == 'true') {
const url = new URL(json.noerror.okay.path);
let resultD = await httpsGetIntent(handlerInput, url, function(resultData) {
if (resultData) {
return resolve(resultData);
} else {
return resolve(handlerInput.responseBuilder.speak('False Test').getResponse());
}
})
}
})
}).then((data) => {
return handlerInput.responseBuilder.speak(data.response.outputSpeech.text).getResponse();
});
},
};
Almost_Ashleigh, based on your own answer, some ideas in which I have :
made the assumption that httpsGetIntent() returns a Promsie that delivers resultData.
promisified checkAuthenticationStatus() by writing the adaptor checkAuthenticationStatusAsync().
consolidated the .speak() commands into final .then() and .catch() clauses.
allowed specific errors to be tailored by decorating with a .title property, which is used in the final .catch() as a cardTitle. Any undecorated errors will default to 'Sorry' (or whatever you want).
// in a suitable scope ...
function checkAuthenticationStatusAsync(handlerInput) {
return new Promise((resolve, reject) {
checkAuthenticationStatus(handlerInput, (json) => {
if (json.error || !json.noerror || json.noerror.okay !== 'true') {
let err = new Error(messages.NO_ACCESS); // or maybe a separate error message per error case?
err.title = 'Unauthorized Request';
reject(err);
} else {
resolve(json);
}
});
});
}
// ... and ...
const IntentRequest = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'IntentRequest';
},
handle(handlerInput) {
return checkAuthenticationStatusAsync(handlerInput)
.then((json) => httpsGetIntent(handlerInput, new URL(json.noerror.okay.path)))
.then((resultData) => {
if (resultData) {
// success path ends here
return handlerInput.responseBuilder.speak(resultData.response.outputSpeech.text).getResponse();
} else {
throw new Error('No result data returned'); // throws to the .catch() below
}
})
.catch(err => {
// all errors end up here.
return handlerInput.responseBuilder.speak(err.message).withSimpleCard(err.title || 'Sorry', err.message).getResponse();
throw err; // to keep handle's caller informed
});
},
};
Note, this is a way, not necessarily the way, to write the code. Please feel free to raid for ideas.
Your best friend is async/await. Please use something like this or like this to access APIs.

use answer .then () outside and use in another part of the code in node js

How can I use the answer of a promise outside of. Then what should I do?
arreglo.forEach((item) => {
if (item.tipoCampo == 3) {
self.campoSelects(item.tablaCampo)
.then(resp => {
console.log(resp)
})
.catch(e => console.log(e))
}
});
console.log (resp) inside the .then () knows it and prints correctly, but when I want to know resp out of the forEach to use below, it says undefined
Thanks.
arreglo.forEach((item) => {
if (item.tipoCampo == 3) {
self.campoSelects(item.tablaCampo)
.then(resp => {
logMyData(resp);
})
.catch(e => console.log(e))
}
});
logMyData=(x)=>{
console.log(x);
}
This is just as simple as adding a helper function which executes inside your .then
Guessing that you want to be able to access the value within the forloop. Since self.campoSelects is a promise we can use async await.
// Call campo selects
function getCampoSelects(_self, tablaCampo) {
return new Promise(async (resolve, reject) => {
let campoData;
try {
campoData = await _self.campoSelects(tablaCampo);
} catch (err) {
reject(err);
}
resolve(campoData);
});
}
function happyLittleFunc() {
const arreglo = [];
arreglo.forEach(async (item) => {
if (item.tipoCampo === 3) {
let campoSelect;
// Unsure if you are setting self somewhere but it can be passed in here.
try {
campoSelect = await getCampoSelects(self, item.tipoCampo);
} catch (err) {
console.log(err);
return;
}
console.log(campoSelect);
}
});
}
happyLittleFunc();

How to hold a NodeJS application until other promise completes

Using promises with NodeJS, I load a model that can then be re-used by susequent calls to the NodeJS app. How can I prevent the same object/model being loaded twice from a database if a second request arrives while the first is still being loaded?
I set a "loading flag" to say that the object is being retrieved from the database and "loaded" when done. If there is a second request that attempts to load the same object, it needs to wait until the initial model is filled and then both can use the same object.
Sample Code (simplified, ES6, Node 0.10 [old for a reason]).
It's the TODO that needs solving.
App:
import ClickController from './controllers/ClickController'
import express from 'express'
const app = express()
app.get('/click/*', (req, res) => {
// Get the parameters here
let gameRef = "test";
ClickController.getGameModel(gameRef)
.then(() => {
console.log('Got game model')
return this.handleRequest()
}, (err) => {
throw err
})
}
Controller:
import gameModel from '../models/GameModel'
class ClickController {
constructor(config) {
// Stores the objects so they can be re-used multiple times.
this.loadedGames = {}
}
// getGameModel() as a promise; return model (or throw error if it doesn't exist)
getGameModel(gameRef) {
return new Promise((resolve, reject) => {
let oGame = false
if(typeof this.loadedGames[gameRef] === 'undefined') {
oGame = new gameModel()
this.loadedGames[gameRef] = oGame
} else {
oGame = this.loadedGames[gameRef]
}
oGame.load(gameRef)
.then(function() {
resolve()
}, (err) => {
reject(err)
})
})
}
}
Model / Object:
class GameModel {
constructor {
this.loading = false
this.loaded = false
}
load(gameRef) {
return new Promise((resolve, reject) => {
if (this.loading) {
// TODO: Need to wait until loaded, then resolve or reject
} else if (!this.loaded) {
this.loading = true
this.getActiveDBConnection()
.then(() => {
return this.loadGame(gameRef)
}, (err) => {
console.log(err)
reject(err)
})
.then(() => {
this.loading = false
this.loaded = true
resolve()
})
} else {
// Already loaded, we're fine
resolve()
}
})
}
// As this uses promises, another event could jump in and call "load" while this is working
loadGame(gameRef) {
return new Promise((resolve, reject) => {
let sql = `SELECT ... FROM games WHERE gameRef = ${mysql.escape(gameRef)}`
this.dbConnection.query(sql, (err, results) => {
if (err) {
reject('Error querying db for game by ref')
} else if (results.length > 0) {
// handle results
resolve()
} else {
reject('Game Not Found')
}
})
})
}
}
I don't follow exactly which part of you're code you are asking about, but the usual scheme for caching a value with a promise while a request is already "in-flight" works like this:
var cachePromise;
function loadStuff(...) {
if (cachePromise) {
return cachePromise;
} else {
// cache this promise so any other requests while this one is stil
// in flight will use the same promise
cachePromise = new Promise(function(resolve, reject) {
doSomeAsyncOperation(function(err, result) {
// clear cached promise so subsequent requests
// will do a new request, now that this one is done
cachePromise = null;
if (err) {
reject(err);
} else {
resolve(result);
}
});
});
return cachePromise;
}
}
// all these will use the same result that is in progress
loadStuff(...).then(function(result) {
// do something with result
});
loadStuff(...).then(function(result) {
// do something with result
});
loadStuff(...).then(function(result) {
// do something with result
});
This keeps a cached promise and, as long as request is "in-flight", the cachePromise value is in place and will be returned by subsequent requests.
As soon as the request actually finishes, the cachePromise will be cleared so that the next request that comes later will issue a new request.

Resources