How to hold a NodeJS application until other promise completes - node.js

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.

Related

How to pass variable into page.on('dialog')?

How do I pass additional variables into a page.on('dialog') function?
How do I get the return from the function?
page.on('dialog', async (dialog) => {
console('get additional variables:', param) // get additional variables
if (dialog.message() === param) {
res.isSuccess = true;
await dialog.accept();
}
else {
res.isSuccess = false;
await dialog.accept();
}
}, p);
console('get return:', res) // Need to get res.isSuccess
I assume you need to intercept the first dialog event. Otherwise isSuccess loses any logical meaning. The interception needs to be performed in a synchronized manner to allow for a testing logic.
Promises are a good solution in your case.
Opt 1 - Wrap in promise and test
function dialog(inputValue) {
return new Promise((resolve, reject) => {
page.once('dialog', async dialog => {
console.log('additional', inputValue);
if (dialog.message() === inputValue) {
await dialog.accept();
resolve();
} else {
await dialog.accept();
reject();
}
});
})
}
Then in your code, you can use it like:
try {
await dialog('Some message');
console.log('OK');
} catch (e) {
console.log('ERROR');
}
Opt 2 - Create a promise
Create a promise wrapper that intercepts the first dialog and returns it:
function firstDialog() {
return new Promise(resolve => {
page.once('dialog', async dialog => {
await dialog.accept();
resolve(dialog);
});
})
}
Then in your code simply test whatever you need:
const dialog = await firstDialog();
if (dialog.message() === 'Some message') {
console.log('OK');
} else {
console.log('ERROR');
}
Code has not been tested, minor changes may be required

Node.js asynchronous and synchronous integration

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.

SyntaxError: Unexpected reserved word "await", node.js is correct version

I am trying to run a function asynchronously 20 times.
I have the function definition:
import axios from 'axios';
async function updateUser (url, user) {
return new Promise((resolve, reject) => {
axios.put(url, JSON.stringify(user))
.then(response => {
//Folio returned code 204
console.log('The user has been updated');
resolve(response.data);
}).catch((err) => {
//Folio returned error
console.error(`Error Text: ${err.response.data}`);
console.error(`Error Code: ${err}`);
});
});
};
export {updateUser};
Then I am trying to loop through users in an external JSON, and update them to my system 20 at a time:
import {updateUser} from './updateUser.js';
import usersjson from './jsons/users.json';
let promises=[];
if (usersjson.users.length) {
try {
for (const user of usersjson.users) {
update = new promise ((resolve,reject) => {
updateUser(url, user)
.then((list) => {resolve(list)})
.catch((error)=>{reject(error)})
});
promises.push(update);
if (promises.length = 20) {
await Promise.all(promises)
.then((responses)=> {
console.log(responses);
promises=[];
}).catch((err)=> {
console.log(err);
});
}
}
} catch(err)=> {
console.log(err);
}
}
However, I am getting an error message in the console:
await Promise.all(promises)
^^^^^
SyntaxError: Unexpected reserved word
My node version is 12.19.0 (based on previous questions, it seems it has to be at least 10, so that shouldn't be the issue).
When I run the function without await, they work correctly (i.e. the users are updated on my system).
It also seems that the for loop is asynchronous by itself, but I'm afraid that if there will be too many calls simultaneously my system will block the API, so I want to make 20-50 calls at a time, tops.
In order to use await you need to declare the function in which you are putting the await as async
For example:
const functionReturningPromise = (input) => {
return new Promise((resolve, reject) => {
if (!input) {
return reject();
}
return resolve();
});
}
const functionAwaitingPromise = async () => {
for (let i = 0; i < 20; i +=1) {
try {
await functionReturningPromise(i);
console.log(i);
} catch (error) {
console.log(error);
}
}
}

problems Setting a interval or timeout on my function

I have a system that checks a database to see if their UserToken is in the database, If it's not it will stop the bot and display an error message, I'm trying to make the bot repeat the same function every minute to see if my database has been updated. Here is the code I'm using:
setInterval(() => {
const getToken = dtoken => new Promise((resolve, reject) => {
MongoClient.connect(url, {
useUnifiedTopology: true,
}, function(err, db) {
if (err) {
reject(err);
} else {
let dbo = db.db("heroku_fkcv4mqk");
let query = {
dtoken: dtoken
};
dbo.collection("tokens").find(query).toArray(function(err, result) {
resolve(result);
});
}
})
})
bot.on("ready", async message => {
const result = await getToken(dtoken)
if (result.length == 1) {
return
} else {
console.error('Error:', 'Your token has been revoked.')
bot.destroy()
}
})
}, 5000);
But it doesn't work and I keep getting this error message:
(node:9808) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 ready listeners added to [Client]. Use emitter.setMaxListeners() to increase limit
if I could get some help with the timeout that would be amazing.
Bot object listens to the event ready on each execution in setInterval(). So after every 5 seconds, a new listener is being added on bot object which you have never removed. That's why it is throwing an error that the maximum limit has been reached.
I think you can take the listener out of the setInterval. It will work.
Updated Code:::
let isReady = false;
bot.on("ready", () => {
isReady = true;
});
const getToken = dtoken => new Promise((resolve, reject) => {
MongoClient.connect(url, {
useUnifiedTopology: true,
}, function(err, db) {
if (err) {
reject(err);
} else {
let dbo = db.db("heroku_fkcv4mqk");
let query = {
dtoken: dtoken
};
dbo.collection("tokens").find(query).toArray(function(err, result) {
resolve(result);
});
}
})
})
setInterval(() => {
if (isReady) {
const result = await getToken(dtoken)
if (result.length == 1) {
return
} else {
console.error('Error:', 'Your token has been revoked.')
isReady = false
bot.destroy()
}
}
}, 5000);

Using async await properly in node js

To overcome callback hell in javascript, I'm trying to use async await from legacy code written in SQLServer procedure.
But I'm not sure my code might be write properly.
My first confusing point is when async function returns, should it return resolve() as boolean, or just return reject and handle with try-catch?
Here is my code snippets.
Please correct me to right direction.
apiRoutes.js
app.route('/api/dansok/cancelDansok')
.post(dansokCancelHandler.cancelDansok);
dansokCancelController.js
const sequelize = models.Sequelize;
const jwt = require('jsonwebtoken');
async function jwtAccessAuthCheck(accessToken) {
if (!accessToken) {
return Promise.reject('Empty access token');
}
jwt.verify(accessToken,"dipa",function(err){
if(err) {
return Promise.reject('TokenExpiredError.');
} else {
return Promise.resolve();
}
});
}
async function checkFeeHist(dansokSeqNo) {
let feeHist = await models.FeeHist.findOne({
where: { DansokSeqNo: dansokSeqNo}
});
return !!feeHist;
}
async function getNextDansokHistSerialNo(dansokSeqNo) {
....
}
async function getDansokFee(dansokSeqNo) {
....
}
async function doCancel(dansokSeqNo) {
try {
if (await !checkFeeHist(dansokSeqNo)) {
log.error("doCancel() invalid dansokSeqNo for cancel, ", dansokSeqNo);
return;
}
let nextDansokSerialNo = await getNextDansokHistSerialNo(dansokSeqNo);
await insertNewDansokHist(dansokSeqNo, nextDansokSerialNo);
await updateDansokHist(dansokSeqNo);
await updateVBankList(dansokSeqNo, danokFee.VBankSeqNo);
await getVBankList(dansokSeqNo);
} catch (e) {
log.error("doCancel() exception:", e);
}
}
exports.cancelDansok = function (req, res) {
res.setHeader("Content-Type", "application/json; charset=utf-8");
const dansokSeqNo = req.body.DANSOKSEQNO;
const discKindCode = req.body.HISTKIND;
const worker = req.body.PROCWORKER;
const workerIp = req.body.CREATEIP;
const accessToken = req.headers.accesstoken;
//check input parameter
if (!dansokSeqNo || !discKindCode || !worker || !workerIp) {
let e = {status:400, message:'params are empty.'};
return res.status(e.status).json(e);
}
try {
jwtAccessAuthCheck(accessToken)
.then(() => {
log.info("jwt success");
doCancel(dansokSeqNo).then(() => {
log.info("cancelDansok() finish");
res.status(200).json({ message: 'cancelDansok success.' });
});
});
} catch(e) {
return res.status(e.status).json(e);
}
};
You'll need to rewrite jwtAccessAuthCheck(accessToken) so that it keeps track of the outcome of its nested tasks. In the code you've written:
// Code that needs fixes!
async function jwtAccessAuthCheck(accessToken) {
// This part is fine. We are in the main async flow.
if (!accessToken) {
return Promise.reject('Empty access token');
}
// This needs to be rewritten, as the async function itself doesn't know anything about
// the outcome of `jwt.verify`...
jwt.verify(accessToken,"dipa",function(err){
if(err) {
// This is wrapped in a `function(err)` callback, so the return value is irrelevant
// to the async function itself
return Promise.reject('TokenExpiredError.');
} else {
// Same problem here.
return Promise.resolve();
}
});
// Since the main async scope didn't handle anything related to `jwt.verify`, the content
// below will print even before `jwt.verify()` completes! And the async call will be
// considered complete right away.
console.log('Completed before jwt.verify() outcome');
}
A better rewrite would be:
// Fixed code. The outcome of `jwt.verify` is explicitly delegated back to a new Promise's
// `resolve` and `reject` handlers, Promise which we await for.
async function jwtAccessAuthCheck(accessToken) {
await new Promise((resolve, reject) => {
if (!accessToken) {
reject('Empty access token');
return;
}
jwt.verify(accessToken,"dipa",function(err){
if(err) {
reject('TokenExpiredError.');
} else {
resolve();
}
});
});
// We won't consider this async call done until the Promise above completes.
console.log('Completed');
}
An alternate signature that would also work in this specific use case:
// Also works this way without the `async` type:
function jwtAccessAuthCheck(accessToken) {
return new Promise((resolve, reject) => {
...
});
}
Regarding your cancelDansok(req, res) middleware, since jwtAccessAuthCheck is guaranteed to return a Promise (you made it an async function), you'll also need to handle its returned Promise directly. No try / catch can handle the outcome of this asynchronous task.
exports.cancelDansok = function (req, res) {
...
jwtAccessAuthCheck(accessToken)
.then(() => {
log.info("jwt success");
return doCancel(dansokSeqNo);
})
.then(() => {
log.info("cancelDansok() finish");
res.status(200).json({ message: 'cancelDansok success.' });
})
.catch(e => {
res.status(e.status).json(e);
});
};
I strongly suggest reading a few Promise-related articles to get the hang of it. They're very handy and powerful, but also bring a little pain when mixed with other JS patterns (async callbacks, try / catch...).
https://www.promisejs.org/
Node.js util.promisify

Resources