I have a collection accounts structured as follow:
Now I have a user who owns two accounts:
How do I make a query to fetch the account of this user and return it as a resolution of a promise?
Here is what I tried. It returns []
getAccounts(user_id, userRef) {
return new Promise((res, rej) => {
this.db.runTransaction((transaction) => {
return transaction.get(userRef.doc(user_id)).then((userDoc) => {
let accounts = []
if (!userDoc.exists) {
throw "User Document does not exist!";
}
let userData = userDoc.data()
let accountList = userData.accounts
for (var id in accountList){
transaction.get(this.ref.doc(id)).then(ans => {
accounts.push(ans.data())
}).catch(e => {
console.log(e)
})
}
return accounts
}).then(function (ans) {
res(ans)
}).catch((e) => {
console.log(e)
});
}).catch(function (err) {
console.error(err);
rej(err)
});
})
}
You don't need to use a transaction, since you are just reading some documents. Since you want to execute, in parallel, two (or more) asynchronous methods which return a promise (i.e. the two get() for the accounts docs) you should use Promise.all().
Something along the following lines should work:
getAccounts(user_id, userRef) {
return db.collection('users').doc(user_id).get() //replaced since I am not sure what is this.db in your case
.then(userDoc => {
const promises = []
if (!userDoc.exists) {
throw "User Document does not exist!";
}
let userData = userDoc.data()
let accountList = userData.accounts
for (var id in accountList){
promises.push(db.collection('accounts').doc(id).get())
})
return Promise.all(promises)
})
.then((results) => {
return results.map(doc => doc.data());
})
.catch(err => {
....
});
}
Note that I have used the "classic" definition for the DocumentReferences (i.e. db.collection('users').doc(user_id) and db.collection('accounts').doc(id)) since I am not 100% sure what are this.ref and this.db in your case. Just adapt as you wish!
You may also rework it with return new Promise((res, rej) => {}) as you wish but the overall philosophy remains the same.
Related
I am trying to push the fetched data in an array using foreach but it only returns the first data in the loop. Here is my code.
exports.getAllTrial = async function (req, res, next) {
try {
new Promise( async (resolve, reject) => {
var reservations = [];
await Schedule.getSchedule()
.then(data => {
data.forEach(async (element) => {
await saveReserve.getAllTrial({where: {scheduleID: element.id, date: "8/18/2020"}})
.then(trial => {
trial.forEach(response => {
reservations.push(response.scheduleID)
})
})
console.log(reservations);
resolve(reservations);
})
});
})
.then(value=>{
res.status(200).json(value);
})
.catch(err => {
console.log(err);
});
} catch (e) {
return res.status(400).json({ status: 400, message: e.message });
}
}
My expected output should be: [ 9, 10, 10 ] But it only returns [9].
Async code in a foreach loop is a bad idea, as it won't be executed one after the other. I suggest reading a bit more async/await and the concept of promise, as you are mixing things here (such as mixing await and .then). Also worth looking into Promise.all which will resolve a list of promises and array.map.
While I have no idea of what some variables such as saveReserve are supposed to be or do, your code might be simplified into:
exports.getAllTrial = async (req, res, next) => {
try {
const data = await Schedule.getSchedule()
const reservations = await Promise.all(
data.map(element => {
return saveReserve.getAllTrial({ where: { scheduleID: element.id, date: '8/18/2020' } })
})
)
return res.status(200).json(reservations)
} catch (e) {
return res.status(400).json({ status: 400, message: e.message })
}
}
I want to refactor code for chain of promises by async, await. I have sequelize ORM for DB management and the code is written in AWS Lambda function having multiple middleware. In such cases I have to traverse code for multiple entries using sequelize transactions. It is easy to manage using promise.all() but need to change it to async await syntax for cleaner code.
Here are my demo code.
/* get all invoice where user_id === current logged in user, and where status != "paid" */
db.Invoice.findAll({
where: {
user_id: currentLoggedInUser,
status: {
$ne: "paid"
}
}
}).then(invoices => {
if (!invoices || invoices === null) {
return false;
}
function addScheduledTransactionAttempts(invoice, tryPayOnDate, t) {
return new Promise((resolve, reject) => {
/* check If any ScheduledTransactionAttempts exists for this invoice.id */
db.ScheduledTransactionAttempts.find({
where: {
invoice_id: invoice.id
}
})
.then(function(attempts) {
if (attempts) {
attempts
.destroy({}, {
transaction: t
})
.then(deletedAttempts => {
console.log("Attempts Record Deleted: ", deletedAttempts);
})
.catch(error => {
reject(error);
t.rollback();
});
}
return db.ScheduledTransactionAttempts.create({
invoice_id: invoice.id,
payment_source_id: PaymentMethodId,
try_pay_on_date: tryPayOnDate,
stripe_customer_id: currentLogInStripeCustomerId
}, {
transaction: t
})
.then(function(attempt) {
resolve(attempt.id);
})
.catch(error => {
reject(error);
t.rollback();
});
})
.catch(error => {
reject(error);
t.rollback();
});
});
}
//Run transaction to addScheduledTransactionAttempts
return db.sequelize.transaction().then(function(t) {
let promiseArr = [];
var i = 0;
invoices.forEach(function(invoice) {
var schedulePaymentDate = moment(paymentDate);
if (invoice) {
let tryPayOnDate = schedulePaymentDate
.add(i, "month")
.format("YYYY-MM-DD");
promiseArr.push(
addScheduledTransactionAttempts(invoice, tryPayOnDate, t) //calling above function
);
i++;
}
});
//now execute promise all
Promise.all(promiseArr)
.then(function(result) {
t.commit();
return true;
})
.catch(function(err) {
t.rollback();
return false;
});
});
});
In the above code I want to change
Promise.all(promiseArr)
which is calling
addScheduledTransactionAttempts
function to do DB queries to simple async function await process to make it easy simpler understandable without having multiple .then or .then inside then promises.
Any help regarding would be appreciated,
Thanks.
It's quite simple. await is valid when invoking methods that return a Promise. All of your SDK methods already return a promise, so refactoring should be quite straight forward. Here's something to get you off ground:
const processInvoices = async currentLoggedInUser {
const invoices = await db.Invoice.findAll({
where: {
user_id: currentLoggedInUser,
status: {
$ne: 'paid',
},
},
});
if (yourOwnLogicForInvoicesObject) {
for (const invoice of invoices) {
const potentiallyFoundInvoice = await db.ScheduledTransactionAttempts.find({
where: {
invoice_id: invoice.id,
},
});
if (potentiallyFoundInvoice) {
await addScheduledTransactionAttempts(potentiallyFoundInvoice)
}
}
}
}
const addScheduledTransactionAttempts = async invoice => {
console.log('Do something with your invoice', invoice)
}
Long story short: refactor the code inside your functions into smaller functions and make these new functions async, just like I did with addScheduledTransactionAttempts and processInvoices
More on async/await
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();
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);
})
}
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.