VS Code debugger not working as expected while debugging Nodejs app - javascript

I am debugging a node app and it has a method deleteProduct. Inside this method is two static methods Cart.deleteProductFromCart(productID, product.price) and Product.deleteProductWithId(productID) that read files from the filesystem and write to the file.
Using the VSCode built-in debugger, When I set the breakpoints to see if the callbacks are getting called back properly, only the callback fired from the first method gets registered whereas the callback from the second method never gets called. I also experimented by changing the order of the two methods. And, the result is that only the method which gets called first has its callback registered whereas the callback from the second method is never called. This happens only during the debug session. The program runs as expected with yarn start though.
So, it's really hard to debug the whole process.
I am not sure if this is an issue with the vs-code debugger or something I am doing wrong due to which the callback is not being registered during the debugging process.
URL to the repository is attached here
const deleteProduct = async (req, res, next) => {
const productID = req.params.productID;
const product = await Product.fetchProductWithId(productID);
Cart.deleteProductFromCart(productID, product.price);
Product.deleteProductWithId(productID);
res.redirect(301, '/products');
};
Cart Model
const fs = require('fs');
const path = require('path');
const rootDir = require('../utils/path');
const filePath = path.join(rootDir, 'data', 'cart.json');
class Cart {
static addProduct(productID, productPrice) {
fs.readFile(filePath, (err, data) => {
let cart = { items: [], totalPrice: 0 };
if (!err && data?.length > 0) {
cart = JSON.parse(data);
}
const existingProduct = cart.items.find((item) => item.id === productID);
if (existingProduct) {
existingProduct.quantity++;
} else {
const product = { id: productID, quantity: 1 };
cart.items.push(product);
}
cart.totalPrice = cart.totalPrice + +productPrice;
console.log(existingProduct === cart.items[0]);
fs.writeFileSync(filePath, JSON.stringify(cart));
});
}
// Delete logic
static deleteProductFromCart(productID, productPrice) {
fs.readFile(filePath, (err, data) => {
// Attached breakpoints within this callback
if (err) {
return;
}
const cart = JSON.parse(data);
const product = cart.items.find((item) => item.id === productID);
if (product) {
cart.totalPrice = cart.totalPrice - product.quantity * +productPrice;
cart.items = cart.items.filter((item) => item.id !== productID);
}
fs.writeFileSync(filePath, JSON.stringify(cart));
});
}
}
module.exports = { Cart };
Product Model
const fs = require('fs');
const path = require('path');
const { v4: uuidv4 } = require('uuid');
const rootDir = require('../utils/path');
const filePath = path.join(rootDir, 'data', 'products.json');
class Product {
constructor(id, title, price, imageURL, description) {
this.id = id;
this.title = title;
this.price = price;
this.imageURL = imageURL;
this.description = description;
}
save() {
fs.readFile(filePath, (err, data) => {
let products = [];
if (!err && data.length > 0) {
products = JSON.parse(data);
}
if (this.id) {
const productIndex = products.findIndex((item) => item.id === this.id);
products[productIndex] = this;
} else {
this.id = uuidv4();
products.push(this);
}
fs.writeFileSync(filePath, JSON.stringify(products));
});
}
static deleteProductWithId(id) {
fs.readFile(filePath, (err, data) => {
// Attached callback within this callback
let products = [];
if (!err && data.length > 0) {
products = JSON.parse(data);
}
const productIndex = products.findIndex((item) => item.id === id);
products.splice(productIndex, 1);
fs.writeFileSync(filePath, JSON.stringify(products));
});
}
static async fetchProductWithId(id) {
return new Promise((resolve, reject) => {
try {
fs.readFile(filePath, (err, data) => {
if (data) {
const products = JSON.parse(data);
const product = products.find((item) => item.id === id);
resolve(product);
}
resolve({
id: null,
title: 'null',
price: 0,
description: null,
imageURL: null,
});
});
} catch {
reject(err);
}
});
}
static async fetchAll() {
return new Promise((resolve, reject) => {
try {
fs.readFile(filePath, (err, data) => {
if (data.length > 0) {
resolve(JSON.parse(data));
}
resolve([]);
});
} catch {
reject(err);
}
});
}
}
module.exports = { Product };
lauch.json file
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "pwa-node",
"request": "launch",
"name": "Launch Program",
"skipFiles": ["<node_internals>/**"],
"program": "${workspaceFolder}/app.js",
// Auto Restart Debugger on file Change
// nodemon should be installed globally
// Also integrated console should be used so that they both listen to same port or something like that
"restart": true,
"runtimeExecutable": "nodemon",
"console": "integratedTerminal"
}
]
}
To Reproduce
Steps to reproduce the behavior:
Attach breakpoints inside callback of readFile as shown in the snippet in both the static methods
Run the debugger
Observe Callstack or Run Step Over, Step Into to see the program flow
Observe that callback from the second method which read file from the filesystem is never reached till the end of program execution.

Related

How to read multiple json file using fs and bulk request

I'm using elasticsearch search engine with my react app, I was reading one file at the backend as you see in the code and it work perfectly, but now I want to read three different JSON files to three different indexes using the "fs" package and bulk request, can you please help me?
the code:
// Start reading the json file
fs.readFile("DocRes.json", { encoding: "utf-8" }, function (err, data) {
if (err) {
throw err;
}
// Build up a giant bulk request for elasticsearch.
bulk_request = data.split("\n").reduce(function (bulk_request, line) {
var obj, ncar;
try {
obj = JSON.parse(line);
} catch (e) {
console.log("Done reading 1");
return bulk_request;
}
// Rework the data slightly
ncar = {
id: obj.id,
name: obj.name,
summary: obj.summary,
image: obj.image,
approvetool: obj.approvetool,
num: obj.num,
date: obj.date,
};
bulk_request.push({
index: { _index: "ncar_index", _type: "ncar", _id: ncar.id },
});
bulk_request.push(ncar);
return bulk_request;
}, []);
// A little voodoo to simulate synchronous insert
var busy = false;
var callback = function (err, resp) {
if (err) {
console.log(err);
}
busy = false;
};
// Recursively whittle away at bulk_request, 1000 at a time.
var perhaps_insert = function () {
if (!busy) {
busy = true;
client.bulk(
{
body: bulk_request.slice(0, 1000),
},
callback
);
bulk_request = bulk_request.slice(1000);
console.log(bulk_request.length);
}
if (bulk_request.length > 0) {
setTimeout(perhaps_insert, 100);
} else {
console.log("Inserted all records.");
}
};
perhaps_insert();
});
You can create multiple promises for each file read and feed it to the elastic search bulk_request.
const fsPromises = require('fs').promises,
files = ['filename1', 'filename1'],
response = [];
const fetchFile = async (filename) => {
return new Promise((resolve, reject) => {
const path = path.join(__dirname, filename);
try {
const data = await fsPromises.readFile(path)); // make sure path is correct
resolve(data);
} catch (e) {
reject(e)
}
});
files.forEach((fileName) => results.push(fetchFile()));
Promise.all(results).then(data => console.log(data)).catch(e => console.log(e));
}
Once you get data from all the promises pass it to the elastic search.

Why does my program crash when managing this JSON file?

Whenever somebody sends !setmainchannel, my program crashes. I am using Node.js with the package discord.js. This is the only problem I have. I don't and can't understand what's wrong, and I would extremely appreciate it if someone could help me out.
bot.js:
let Discord = require("discord.js");
let client = new Discord.Client();
let fs = require('fs');
let help = require("./helpembed.json");
var jsonRead = function(filePath){
fs.readFile(filePath, 'utf-8', (err, returnJson) => {
if(err){
console.log(err);
}
else{
let ret = JSON.parse(returnJson);
return ret;
}
})
}
var jsonWrite = function(filePath, objIn){
fs.writeFile(filePath, JSON.stringify(objIn), err => {
if(err){
console.log(err);
}
})
}
client.on("message", msg => {
let time = new Date();
let cstTimeStamp = `${time.getMonth() + 1}/${time.getDate()}/${time.getFullYear()} ${time.getHours() + 1}:${time.getMinutes()}:${time.getSeconds()}`
if(msg.content == "!setmainchannel"){
let mainChannel = msg.mentions.channels.first();
if(!mainChannel){
console.log(`${cstTimeStamp} #${msg.author.tag} requested to set a main channel but didn't provide a channel\n`);
msg.channel.send("There's no channel to set the main to");
}
else{
let currentSettings = jsonRead("./main-channel.json");
currentSettings.channel = mainChannel;
jsonWrite("./main-channel.json", currentSettings);
console.log(`${cstTimeStamp} #${msg.author.tag} set the main channel as ${currentSettings.channel}\n`);
msg.channel.send(`Set the main channel as ${currentSettings.channel}`);
}
}
})
client.once('ready', () => {
console.log('Bot is online\n');
});
client.login('Token hidden for safety reasons');
main-channel.json:
{
"channel":null
}
First, your bot does nothing because it only reacts if (msg.content == "!setmainchannel"). If you provide an argument, like a channel (!setmainchannel #ch-name), it won't run as the message's content is not exactly the prefix and the command.
You'll need to fix that first. By removing the prefix and chopping off the arguments, you can get the command itself and check if this exact command is used. (Again, you should not check the whole message.content)
Second, you don't wait for the callback inside readFile to run. It means, when you try to update currentSettings.channel, your jsonRead() function has just started to read the file and the currentSettings is undefined. You won't be able to update its channel property.
I've updated both your jsonWrite and jsonRead functions to return promises and make it easier to use. Check the working code below:
const Discord = require("discord.js");
const fs = require('fs');
const path = require('path');
const client = new Discord.Client();
const prefix = '!';
client.on('message', async (msg) => {
// create an args variable that slices off the prefix and splits it into an array
const args = msg.content.slice(prefix.length).split(/ +/);
// create a command variable by taking the first element in the array
// and removing it from args
const command = args.shift().toLowerCase();
const cstTimeStamp = new Date().toLocaleString();
if (command === 'setmainchannel') {
const mainChannel = msg.mentions.channels.first();
const filePath = path.resolve(__dirname, './main-channel.json');
if (!mainChannel) {
console.log(`${cstTimeStamp} ${msg.author.tag} requested to set a main channel but didn't provide a channel\n`);
return msg.channel.send("There's no channel to set the main to");
}
try {
const currentSettings = await jsonRead(filePath);
currentSettings.channel = mainChannel;
jsonWrite(filePath, currentSettings);
console.log(`${cstTimeStamp} ${msg.author.tag} set the main channel as ${currentSettings.channel}\n`);
msg.channel.send(`Set the main channel as ${currentSettings.channel}`);
} catch (err) {
console.log(err);
}
}
});
client.once('ready', () => {
console.log('Bot is online\n');
});
client.login('Token hidden for safety reasons');
function jsonRead(filePath) {
return new Promise((resolve, reject) => {
fs.readFile(filePath, 'utf-8', (err, content) => {
if (err) {
reject(err);
} else {
try {
const parsedContent = JSON.parse(content);
resolve(parsedContent);
} catch (err) {
reject(err);
}
}
});
});
}
function jsonWrite(filePath, data) {
return new Promise((resolve, reject) => {
fs.writeFile(filePath, JSON.stringify(data), (err) => {
if (err) {
reject(err);
}
resolve(true);
});
});
}
Alright, I see what's going on here. I made the logic react to ONLY !setmainchannel, whereas I should've done if(msg.content.startsWith("!setmainchannel")!

GraphQl mutation wrong result

I have some troubles with mutating data within Graphql. I am saving the origin image into my database and then mutating it with Graphql (external).
In the following points in the code I get the correct data
####################1111111111111111111####################
####################222222222222222####################
####################333333333333333####################
But at point
####################444444444444444444####################
after I mutate the data I am getting wrong image src. It is the edited image src and not the origin src I retrieved from database in my revertImages() function.
Although I pass the correct variable "newVariable" with correct data, the mutation takes over the mutation function edited data that I had previously passed, but takes over the newVariable data. Do I need to clear the cache maybe?
The newVariable data is:
{
productId: 'gid://shopify/Product/6166892019882',
image: {
altText: '',
id: 'gid://shopify/ProductImage/23268973543594',
src: 'https://cdn.shopify.com/s/files/1/0508/3516/1258/products/180622-05-2.jpg?v=1611416719'
}
}
After mutation the result is:
{
productImageUpdate: {
image: {
altText: null,
id: 'gid://shopify/ProductImage/23268973543594',
src: 'https://cdn.shopify.com/s/files/1/0508/3516/1258/products/180622-05-2.jpg?v=1611416762'
},
userErrors: []
}
}
Here are my functions:
const revertImages = async (ctx) => {
let dataToRevert = ctx.request.body.data;
const { accessToken } = ctx.session;
let productsDoc = await Product.find({ productId: { $in: dataToRevert.productId } });
if (!productsDoc) {
ctx.throw('Could not find products');
}
console.log('####################1111111111111111111####################');
console.log(productsDoc);
console.log('####################1111111111111111111####################');
const res = await revertProductImages(productsDoc, accessToken);
if (res) {
console.log('Products reverted');
ctx.response.status = 200;
}
}
async function revertProductImages(products, accessToken) {
console.log('Revert Product Images')
return new Promise((resolve, reject) => {
const map = {};
let updateProduct = null;
let variables = null;
products.forEach(async (item) => {
map[item.productId] = map[item.productId] + 1 || 1;
variables = {
"productId": `gid://shopify/Product/${item.productId}`,
"image": {
"altText": "",
"id": `gid://shopify/ProductImage/${item.imageId}`,
"src": item.originalSrc
}
};
console.log('####################222222222222222####################');
console.log(variables);
console.log('####################222222222222222####################');
updateProduct = await updateProductImage(UPDATE_PRODUCT_BY_ID, variables, accessToken);
if (updateProduct) {
const res = await removeProductFromDb(map);
if (res) {
resolve(res);
}
}
});
})
}
async function updateProductImage(queryName, variables, token) {
console.log('updateProductImage..');
const newVariable = variables;
console.log('####################333333333333333####################');
console.log(newVariable);
console.log('####################333333333333333####################');
return new Promise(async (resolve, reject) => {
let res = null;
try {
res = await axios({
headers: {
'X-Shopify-Access-Token': token,
},
method: 'post',
data: {
query: queryName,
variables: newVariable,
},
url: url,
});
} catch (err) {
console.log(err.message);
}
if (res) {
console.log('Image updated ✔️');
console.log('####################444444444444444444####################');
console.log(res.data.data);
console.log('####################444444444444444444####################');
resolve(res);
} else {
reject('Can not update image');
}
}).catch((err) => {
console.log(err);
});
}
Maybe I didn't understood correctly but here's an answer...
I cannot find the src property in available fields for this mutation (i'm not talking about the ImageInput but the image object).
Can you try with the following request :
mutation productImageUpdate($productId: ID!, $image: ImageInput!) {
productImageUpdate(productId: $productId, image: $image) {
image {
id
originalSrc
transformedSrc
}
userErrors {
field
message
}
}
}
The docs says :
originalSrc : The location of the original image as a URL.
transformedSrc : The location of the transformed image as a URL.
Maybe what you are expecting is in originalSrc ?

Discord bot post multiple results

First time using stackoverflow. It is a bot made to post result whenever new episode of show in search list gets added on nyaa.si. I want bot to post result only once for every episode but bot post same episode multiple time in different time frames. It gets fixed for while after I restart the bot.
The code to add show to search list.
async addShow(msg) {
const regex = /"(.+?)" "(.+?)"(?: "(.+?)")?/g;
const found = regex.exec(msg.content);
if (found === null) {
await msg.channel.send(`Invalid new syntax:\n${COMMAND_CHARACTER} new \"show search phrase\" \"MALURL\" \"attribute regex\" (optional last)`);
return;
}
let [f, search, url, reg] = found;
let count = await this.db.get('search').last().value();
if (_.isUndefined(count)) {
count = 0;
}
else {
count = count.id;
}
await this.db.get('search').push({id: count + 1, search, url, regex: reg}).write();
logger.info(`New show has been added to the searchlist - ${search} - ${url} for server ${this.guildID}`);
await msg.channel.send("Saved!");
}
The code to search
async searchShow(id, query, channel = null, OG = null) {
const results = await this.nyaa.getResults(query);
if (!results.length) {
return;
}
logger.info(`Results found for ${query}: ${results.length}`);
const embedFunction = this.getRichEmbed.bind(this);
for (let i of results) {
const item = await this.db.get('rss').find({guid: i.guid}).value();
if (!_.isUndefined(item)) {
continue;
}
if (await this.postShow(embedFunction, i, channel, OG)) {
await this.db.get('rss').push({...i, searchID: id}).write();
}
}
}
Code to post result when new episode comes.
async postShow(embedFunc, item, channel = null, og = null, channelType = NYAA_UPDATES) {
if (channel === null) {
channel = await this.getGuildChannel(channelType);
if (!channel) {
return false;
}
}
return new Promise(async (resolve) => {
const title = (og !== null ? og.title ?? item.title : item.title);
const embed = await embedFunc(item, title);
if (og !== null) {
const img = og.image ?? null;
if (img) {
embed.setThumbnail(img);
}
const url = og.url ?? null;
if (url) {
embed.setURL(url);
}
}
let retryCounter = 0;
logger.info(`Posting new result for ${title} with guid ${item.guid} for server ${this.guildID}`);
while (true) {
try {
await channel.send(embed);
setTimeout(() => {
resolve(true);
}, 2000);
break;
}
catch (e) {
logger.warn(`An error has occured while posting: ${e.toString()}, retrying (${++retryCounter} in 5 seconds`);
await new Promise((res) => {
setTimeout(() => {
res();
}, 5000);
});
if (retryCounter > 10) {
resolve(false);
}
}
}
});
}
Also one who wrote most of code was different person and I only added few additional function here and there which doesn't affect the code much. One who wrote most of core code had to leave discord so I was left to host the bot which I am hosting at repl.it. It will be great help to know whether the problem is with the code or not.
As Julian Kleine mentioned above, the most common reason a bot posts multiple times is if you are running multiple instances of the host. Close all instances of command prompt, and check task manager to see if any other hidden instances are running in the background.

Async/await and promises [duplicate]

This question already has answers here:
Using async/await with a forEach loop
(33 answers)
Closed 4 years ago.
I am having an issue with using async/await using node v8.1. It appears my issue is that I am not returning a promise from my async functions. This is causing the flow of the program to run out of order. I thought by making a function async that the function automatically returns a promise, but that is not the case I am running into.
I expect the program below to output:
Validating xlsx file...
(text from validateParsedXlsx)
Adding users to cognito...
(text from addUsersToCognito)
Adding users to dynamodb...
(text from addUsersToDynamodb)
Instead, I get:
Validating xlsx file...
Adding users to cognito...
Adding users to dynamodb...
(text from validateParsedXlsx)
(text from addUsersToCognito)
(text from addUsersToDynamodb)
The issue seems to be pretty obvious, that validateParsedXlsx() addUsersToCognito() and addUsersToDynamodb() are not returning promises. Again, I thought that by using the async keyword, the function automatically took care of this.
Thanks for the help.
Here is my script:
const xlsx = require('xlsx');
const AWS = require('aws-sdk');
AWS.config.update({region: 'us-west-2'});
const documentClient = new AWS.DynamoDB.DocumentClient({convertEmptyValues: true});
async function main(){
if (!process.argv[2]) {
console.log('\nAbsolute filepath missing. Pass the absolute filepath in as command line argument.\n')
process.exit(1);
}
const xlsxFilePath = process.argv[2];
let parsedXlsx = [];
try {
parsedXlsx = parseXlsx(xlsxFilePath);
} catch (error) {
if(error.code === 'ENOENT') {
console.log(`\nThe file path: ${process.argv[2]} cannot be resolved\n`)
} else {
console.log(error);
}
}
console.log('\n\nValidating xlsx file...\n');
await validateParsedXlsx(parsedXlsx);
console.log('\n\nAdding users to cognito...\n');
await addUsersToCognito(parsedXlsx);
console.log('\n\nAdding users to dynamodb...\n');
await addUsersToDynamodb(parsedXlsx);
}
function parseXlsx(filePath) {
const workbook = xlsx.readFile(filePath);
const sheetNameList = workbook.SheetNames;
const parsedXlsxSheets = sheetNameList.map(function (y) {
const worksheet = workbook.Sheets[y];
const headers = {};
const data = [];
for (z in worksheet) {
if(z[0] === '!') continue;
//parse out the column, row, and value
const col = z.substring(0,1);
const row = parseInt(z.substring(1));
const value = worksheet[z].v;
//store header names
if(row == 1) {
headers[col] = value;
continue;
}
if(!data[row]) data[row] = {};
data[row][headers[col]] = value;
}
//drop those first two rows which are empty
data.shift();
data.shift();
return data;
});
return parsedXlsxSheets[0]
}
async function validateParsedXlsx(users) {
let error = false;
users.forEach(async (user, index) => {
if (!user.email) {
console.log(`User at row ${index + 2} doesn't have 'email' entry in xlsx file.`);
error = true;
}
if (!user.displayName) {
console.log(`User at row ${index + 2} doesn't have 'displayName' entry in xlsx file.`);
error = true;
}
if (!user.serviceProviderId) {
console.log(`Userat row ${index + 2} doesn't have 'displayName' entry in xlsx file.`);
error = true;
} else {
const params = {
TableName: 'service-providers',
Key: {
serviceProviderId: user.serviceProviderId
}
}
const response = await documentClient.get(params).promise();
if (!response.Item) {
console.log(`User at row ${index +2} does not have a valid serviceProviderId.`);
error = true;
} else {
console.log(`User ${user.email} is valid, assigned to service provider: ${response.Item.displayName}`);
}
}
if (error) {
console.log(`Every user in xlsx file must have these attributes, spelled correctly: email, displayName, and serviceProviderId\n\nIn addition, make sure the serviceProviderId is correct by checking the service-providers dynanomdb table.`);
process.exit(1);
}
});
}
async function addUsersToCognito(users) {
const cognitoIdentityServiceProvider = new AWS.CognitoIdentityServiceProvider();
const results = await cognitoIdentityServiceProvider.listUserPools({MaxResults: 10}).promise();
let serviceProviderUserPoolId = '';
results.UserPools.forEach((userPool) => {
if(userPool.Name === 'service-provider-users') {
serviceProviderUserPoolId = userPool.Id;
}
});
users.forEach(async (user) => {
const params = {
UserPoolId: serviceProviderUserPoolId,
Username: user.email,
DesiredDeliveryMediums: ['EMAIL'],
TemporaryPassword: 'New_User1',
UserAttributes: [
{
Name: 'email',
Value: user.email
},
{
Name: 'custom:service_provider_id',
Value: user.serviceProviderId
}
]
}
try {
await cognitoIdentityServiceProvider.adminCreateUser(params).promise();
console.log(`Added user ${user.email} to cognito user pool`);
} catch (error) {
if (error.code === 'UsernameExistsException') {
console.log(`Username: ${user.email} already exists. No action taken.`);
}
else {
console.log(error);
}
}
});
}
async function addUsersToDynamodb(users) {
users.forEach(async (user) => {
const params = {
TableName: 'service-provider-users',
Item: {
serviceProviderId: user.serviceProviderId,
userId: user.email,
displayName: user.displayName,
isActive: false,
role: 'BASIC'
},
ConditionExpression: 'attribute_not_exists(userId)'
}
try {
await documentClient.put(params).promise();
console.log(`Added user ${user.email} to dynamodb user table`);
} catch (error) {
if (error.code === 'ConditionalCheckFailedException') {
console.log(`User ${user.email} already in the dynamodb table service-provider-users`);
} else {
console.log(error);
}
}
});
}
main();
users.forEach(async (user, index) => {
That starts a few promising actions but never awaits them. May do:
await Promise.all(users.map(async (user, index) => {
... to execute them in parallel or do this:
await users.reduce((chain, user, index) => async (user, index) => {
await chain;
//...
}, Promise.resolve());
To execute them one after another.
PS: Using process.exit should be the very last option to end your program

Categories

Resources