Firestore Promises - node.js

I'm having trouble understanding this concept implementation as I am new to Firestore. I need to wait on the query before moving through the code and if the foreach condition returns two i'd love for the loop to stop. The condition should return true but instead I get false then true after the query is done
teamexist = (name) => {
var exists = false;
firebase.firestore().collection('teams').get().then(snapshot => {
snapshot.forEach(doc => {
if(doc.data().name === name) {
exists = true;
console.log(exists);
}
});
})
.catch(err => {
console.log('Error getting documents', err);
});
console.log("after each");
if(!exists){
console.log("last");
//this.checkPlayerTeams(firebase.auth().currentUser.uid);
}
console.log(exists);
}
OUTPUT:
after each,
last,
false,
true
DESIRED OUTPUT:
false, true, after each

This is confusion w.r.t Event Loop in Javascript. Basically your code
if(!exists){
console.log("last");
//this.checkPlayerTeams(firebase.auth().currentUser.uid);
}
console.log(exists);
Is Running before
snapshot.forEach(doc => {
if(doc.data().name === name) {
exists = true;
console.log(exists);
}
});
As an Async call is made to FireStore to fetch entities.
Following should work.
snapshot.forEach(doc => {
if(doc.data().name === name) {
exists = true;
console.log(exists);
}
if(!exists){
console.log("last");
//this.checkPlayerTeams(firebase.auth().currentUser.uid);
}
});

I got help from somewhere else. For anyone else that is looking for the same info it was waiting on the Promise to return for a query.
teamexist = async (name) => {
return firebase.firestore().collection('teams').where('name', '==', name).get()
.then(snapshot => {
return !snapshot.empty;
})
.catch(err => {
console.log('Error getting documents', err);
return false;
});
}
this worked great and returned if what I needed existed.

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));
});
});

Saving replaced string data in mongodb using nodejs

I am trying to replace a string in url . Here is image of it
in this image I want to replace lssplalpha with lssplprod which are in pics array. For that I created an api . Here is a code
apiRoutes.get('/SchoolListing_lssplalpha_Replace',function(req, res) { schoolListModel.find({},function(err,check){
if(err){
return console.log(err);
}
else{
for(var i=0;i<check.length;){
var f=0;
for(var j=0;j<check[i].pics.length;j++){
f++;
var newTitle = check[i].pics[j].replace("lssplalpha","lsslprod");
check[i].pics[j] = newTitle;
console.log("after change",check[i].pics[j]);
check[i].save(function (err) {
if(err) {
console.error('ERROR!');
}
});
}
if(j==check[i].pics.length&&j==f){
i++;
}
}
console.log("i value",i);
console.log("check length",check.length);
if(i==check.length){
return res.json({status:true,message:"Updated Schools"}); }
}
});
});
I am getting success response . When I go and check database nothing changed in db. To know the reason I write log of it. When I see logs it was replacing correctly. But I didn't understand why those are not reflecting in database? Here is an image of log in console
Please help me to come out of this
The issue here is you are running a for loop (synchronous) where you are calling the model.save() operation which is asynchronous and the loop keeps iterating but the results of the async calls come later. The process of saving a database item in an array takes some time and Node.js knows this, so it starts the update and then just moves on trying to update the next item in the array. Once the write operation is complete a callback function is run, but by that point the loop has completed and there is no way to know which items finish in what order.
You could use the Bulk Write API to update your models. This allows you to sends multiple write operations to the MongoDB server in one command. This is faster than sending multiple independent operations (like) if you use create()) because with bulkWrite() there is only one round trip to MongoDB.
The following examples show how you can use the bulkWrite.
Using async/await:
apiRoutes.get('/SchoolListing_lssplalpha_Replace', async (req, res) => {
try {
let ops = [];
const docs = await schoolListModel.find({}).lean().exec();
docs.forEach(doc => {
const pics = doc.pics.map(pic => pic.replace("lssplalpha", "lsslprod"));
ops.push({
"updateOne": {
"filter": { "_id": doc._id },
"update": {
"$set": { pics }
}
}
});
});
const result = await schoolListModel.bulkWrite(ops);
console.log('Bulk update complete.', result);
res.status(200).json({
status: true,
message: "Updated Schools"
});
} catch (err) {
res.status(400).send({
status: false,
message: err
});
}
});
Using Promise API:
const bulkUpdate = (Model, query) => (
new Promise((resolve, reject) => {
let ops = [];
Model.find(query).lean().exec((err, docs) => {
if (err) return reject(err);
docs.forEach(doc => {
const pics = doc.pics.map(pic => (
pic.replace("lssplalpha", "lsslprod")
));
ops.push({
"updateOne": {
"filter": { "_id": doc._id },
"update": {
"$set": { pics }
}
}
});
if (ops.length === 500) {
Model.bulkWrite(ops).then((err, result) => {
if (err) return reject(err);
ops = [];
resolve(result);
});
}
});
if (ops.length > 0) {
Model.bulkWrite(ops).then((err, result) => {
if (err) return reject(err);
resolve(result);
});
}
});
})
);
apiRoutes.get('/SchoolListing_lssplalpha_Replace', (req, res) => {
bulkUpdate(schoolListModel, {}).then(result => {
console.log('Bulk update complete.', result);
res.status(200).json({
status: true,
message: "Updated Schools"
});
}).catch(err => {
res.status(400).send({
status: false,
message: err
});
});
});
You are running asynchronous call model.save() in for loop(synchronous)
To make your for loop synchronous you can use for...of loop which works asynchronous, also you will not need to add multiple checks like you have done in your code.
Try following code, it will work
apiRoutes.get('/SchoolListing_lssplalpha_Replace', function (req, res) {
schoolListModel.find({},function (err, check) {
if (err) {
return console.log(err);
}
else {
for (let checkObj of check) {
let newPicArr=[];
for (let pic of checkObj.pics) {
pic = pic.replace("lssplalpha", "lsslprod");
newPicArr.push(pic);
}
checkObj.pics=newPicArr;
checkObj.save(function (err) {
if (err) {
console.error('ERROR!');
}
});
}
return res.json({ status: true, message: "Updated Schools" });
}
});
});

.then of Promise.all not executing

I'm using firestore to retrieve data which has the following DS.
I have a Company collection which contains a subcollection Branches
So I'm trying to retrieve to list all the Companies with its Branches
Code:
exports.findAll = function (req, res) {
getcompanies().
then((companies) => {
console.log("Main "+ companies) // info: Main TypeError: Cannot read property 'Symbol(Symbol.iterator)' of undefined
return res.json(companies);
})
.catch((err) => {
console.log('Error getting documents', err);
});
}
function getCompanies(){
var companiesRef = db.collection('companies');
return companiesRef.get()
.then((snapshot) => {
let companies = [];
return Promise.all(
snapshot.forEach(doc => {
let company = {};
company.id = doc.id;
company.company = doc.data();
var branchesPromise = getBranchesForCompanyById(company.id);
return branchesPromise.then((branches) => {
company.branches = branches;
companies.push(company);
if(snapshot.size === companies.length){
console.log("companies - Inside" + JSON.stringify(companies)); //This prints all companies with its branches
}
return Promise.resolve(companies);
})
.catch(err => {
console.log("Error getting sub-collection documents", err);
return Promise.reject(err);
})
})
)
.then(companies => {
console.log("Outside " + companies) // This is never executed
return companies;
})
.catch(err => {
return err;
});
})
.catch(err => {
return err;
});
}
function getBranchesForCompanyById(id){
var branchesRef = db.collection('companies').doc(id).collection('branches');
let branches = [];
return branchesRef.get()
.then(snapshot => {
snapshot.forEach(brnch => {
let branch = {};
branch.id = brnch.id;
branch.branch = brnch.data();
branches.push(branch);
})
return branches;
})
.catch(err => {
return err;
})
}
I've all the data needed at this point.
console.log("companies - Inside" + JSON.stringify(companies)); //This prints all companies with its branches
But the then of Promise.all is never executed. So getting this error -
info: Main TypeError: Cannot read property 'Symbol(Symbol.iterator)' of undefined
console.log("Main "+ companies) // info: Main TypeError: Cannot read property 'Symbol(Symbol.iterator)' of undefined
I feel I have followed all the rules specified here: https://stackoverflow.com/a/31414472/2114024 with respect to nested promises, not sure where I'm missing the point.
Thanks in advance!
I see at least 2 problems:
forEach likely doesn't return anything, and you send the result of forEach into Promise.all().
If Promise.all() throws an exception, some of your catch handlers just grab the error and return it. Returning it turns it into a non-exception.
You also really don't have to add a catch to every Promise chain, as long as you feed the result of a Promise chain back into another promise chain, you probably only need 1 catch block.
Also one of your then() functions should not be nested as deeply. Just move it a level up, that's the point of promises.
In your code, you can use map instead of forEach. Promise.all accept an array of promises but forEach does not return an array
return Promise.all(
snapshot.map(doc => {
let company = {};
company.id = doc.id;
company.company = doc.data();
var branchesPromise = getBranchesForCompanyById(company.id);
return branchesPromise.then((branches) => {
company.branches = branches;
companies.push(company);
if (snapshot.size === companies.length) {
console.log("companies - Inside" + JSON.stringify(companies)); //This prints all companies with its branches
}
return Promise.resolve(companies);
})
.catch(err => {
console.log("Error getting sub-collection documents", err);
return Promise.reject(err);
})
})
)
Based on inputs from Evert and Rahul, thanks to both of you, I have resolved the problem here.
I handled all the error in the catch block
Promise.all was not returning anything so I converted the forEach to map.
So this is my updated code, which solves the problem:
exports.findAll = function (req, res) {
getcompanies().
then((companies) => {
console.log("Main " + companies) // Prints all companies with its branches
return res.json(companies);
})
.catch((err) => {
console.log('Error getting documents', err);
return res.status(500).json({ message: "Error getting the all companies" + err });
});
}
function getCompanies() {
var companiesRef = db.collection('companies');
return companiesRef.get()
.then((snapshot) => {
let companies = [];
return Promise.all(
snapshot.docs.map(doc => {
let company = {};
company.id = doc.id;
company.company = doc.data();
var branchesPromise = getBranchesForCompanyById(company.id);
return branchesPromise.then((branches) => {
company.branches = branches;
companies.push(company);
if (snapshot.size === companies.length) {
console.log("companies - Inside" + JSON.stringify(companies));
return companies;
}
})
.catch(err => {
console.log("Error getting sub-collection documents", err);
throw new Error(err);
})
})
)
.then(companies => {
console.log("Outside " + companies); // Executed now
return companies[companies.length - 1];
})
.catch(err => {
throw new Error(err);
});
})
.catch(err => {
throw new Error(err);
});
}
function getBranchesForCompanyById(id) {
var branchesRef = db.collection('companies').doc(id).collection('branches');
let branches = [];
return branchesRef.get()
.then(snapshot => {
snapshot.forEach(brnch => {
let branch = {};
branch.id = brnch.id;
branch.branch = brnch.data();
branches.push(branch);
})
return branches;
})
.catch(err => {
throw new Error(err);
})
}

Data aggregation in NodeJS with promisified functions

I'd like to aggregate data from MongoDB in NodeJS with promisified functions.
My script with dump are here https://github.com/network-spy/lego
Little description: there are 2 collections in database: "zip" and "restaurants". "zip" contains zip codes of locations and "restaurants" contains information about restaurants with zip codes. So script should create new collection "stat" and fill it with documents like:
{"zip_code" : "01002", "restaurants" : [ list of restaurants ] }
The problem is that in "zip" collection there are 29353 documents, but after script processing I get "stat" collection with 29026 documents(sometimes count of documents can change).
I guess it's because of broken synchronization somewhere in my JS code. Could you look please at my code and advice anything how to repair it?
const MongoClient = require('mongodb').MongoClient;
const mongoDbUrl = 'mongodb://127.0.0.1:27017/world';
MongoClient.connect(mongoDbUrl, function(err, db) {
if (err) {
console.log(err);
return;
}
console.log("Connected to server.");
clearStat(db).then(
result => {
console.log(result);
processZips(db).then(
result => {
console.log(result);
closeMongoDBConnection(db);
},
error => {
console.log(error);
closeMongoDBConnection(db);
}
);
},
error => {
console.log(error);
closeMongoDBConnection(db);
}
);
});
let closeMongoDBConnection = (db) => {
db.close();
console.log("Disconnected from server.");
};
let clearStat = (db) => {
return new Promise((resolve, reject) => {
db.collection('stat').deleteMany({}, function(err, results) {
if (err) {
reject(err);
}
resolve('Stat data cleared');
});
});
};
let processZips = (db) => {
return new Promise((resolve, reject) => {
db.collection('zip').find({}, {"_id":1}).each((err, zipCode) => {
if (zipCode == null) {
resolve('Zips precessed');
} else if (err) {
reject(err);
} else {
findRestaurantsByZip(db, zipCode._id).then(
result => {
insertToStat(db, zipCode._id, result).then(
result => {
console.log('Inserted: ');
console.dir(result);
},
error => {
reject(error);
}
);
},
error => {
reject(error);
}
);
}
});
});
};
let findRestaurantsByZip = (db, zipCode) => {
return new Promise((resolve, reject) => {
db.collection('restaurant').find({"address.zipcode": zipCode}).toArray((err, restaurants) => {
if (err) {
reject(err);
}
resolve(restaurants);
});
});
};
let insertToStat = (db, zip, restaurants) => {
return new Promise((resolve, reject) => {
let statDocument = {};
statDocument.zip_code = zip;
statDocument.restaurants = restaurants;
db.collection('stat').insertOne(statDocument).then(
result => {
resolve(statDocument);
},
error => {
reject(error);
}
);
});
};
Firstly, a simplification of your processZips function. This is functionally identical to your code but uses Promise chaining rather than nested Promises
let processZips = (db) => new Promise((resolve, reject) =>
db.collection('zip').find({}, {"_id":1}).each((err, zipCode) => {
if (zipCode == null) {
resolve('Zips precessed');
} else if (err) {
reject(err);
} else {
findRestaurantsByZip(db, zipCode._id)
.then(result => insertToStat(db, zipCode._id, result))
.then(result => console.log('Inserted: ', result))
.catch(error => reject(error));
}
})
);
The problem may be (I can't test anything) that you resolve the processZips promise at the end of the .each processing. This "triggers" the .then that closes the database. However, due to the asynchronous find/insert code it may well be that some of that is "in progress" at the time. I don't profess to know mongodb well, so I don't know what closing the db while processing is still active would do - seems likely that's the reason why you're output data is "short"
So, there's two ways to approach this
1 - process each zipCode in series, i.e. each find/insert waits for the previous to complete, and then resolve when last zipCode is done
let processZips = (db) => {
// set p to a resolved Promise so the first find/insert will kick off
let p = Promise.resolve();
return new Promise((resolve, reject) =>
db.collection('zip').find({}, {"_id":1}).each((err, zipCode) => {
if (zipCode == null) {
// wait for last insert to complete before resolving the Promise
resolve(p.then(() => resolve('Zips precessed'))); // see note 1, 2
} else if (err) {
reject(err);
} else {
// wait for previous insert to complete before starting new find/insert
p = p
.then(() => findRestaurantsByZip(db, zipCode._id))
.then(result => insertToStat(db, zipCode._id, result))
.then(result => console.log('Inserted: ', result)); // see note 1
}
})
);
};
With this code, as soon as a find/insert rejects, no more find/insert will actually be performed
2 - process each code in "parallel", i.e. kick off all the find/insert and then resolve when all zipCode are done
let processZips = (db) => {
// create an array for all the find/insert Promises
let p = [];
return new Promise((resolve, reject) =>
db.collection('zip').find({}, {"_id":1}).each((err, zipCode) => {
if (zipCode == null) {
// wait for all find/insert to complete before resolving this Promise
resolve(Promise.all(p).then(() => 'Zips precessed')); // see note 1, 2
} else if (err) {
reject(err);
} else {
p.push(findRestaurantsByZip(db, zipCode._id)
.then(result => insertToStat(db, zipCode._id, result))
.then(result => console.log('Inserted: ', result))
); // see note 1
}
})
);
};
The one caveat with the second method is, like in your original code, if one of the find/insert fails that wont stop subsequent find/insert from processing.
You'll notice that there seems to be a lack of error handling compared to your original code. This code uses the 2 "features" of promises.
rejections will "flow through" the promise chain,
if you resolve a promise with a rejected promise, it is identical to rejecting the promise.

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