Wait promises inside loop in nodejs - javascript

I know this question may already be asked. But I didn't understand how things are worked.that is why I am creating the new thread.
con.query(sql,[req.params.quizId],(err,rows,fields)=>{
//rows contains questions
if(err) throw err;
else{
let object={};
rows.forEach((item,index)=>{
object=item;
//here iam passing question id to get choices a async function
getChoices(item.id)
.then(data=>{
object.choices=data;
//save the question array
response.push(object);
//res.send(response);
});
})
res.send(response) //return empty array
}
});
function getChoices(questionId) {
let sql='SELECT id,text FROM `question_choices` where question_id=?';
return new Promise((resolve, reject) => {
con.query(sql,[questionId],(err,rows,fields)=>{
if(err) throw err;
else {
resolve(rows);
}
})
})
}
I tried several things but none is worked. I think for loop didn't wait for the promise to complete and it sends the response directly. Some async problems are happening there.
I can able to get all questions from database and for each question I need to get corresponding choices that I want.
something like this
[{id:'xx', text:'yy',choices:[{id:'c',text:'kk']},etc]

forEach runs synchronously. You're looking for Promise.all, which accepts an array of Promises, and resolves to an array of the resolved values once all of the Promises resolve. To transform your rows array to an array of Promises, use .map.
Also, when there's an error, you should call reject so that you can handle errors in the consumer of the Promise (the con.query callback), otherwise, when there's an error, it'll hang forever without you knowing about it:
con.query(sql,[req.params.quizId],(err,rows,fields)=>{
if(err) throw err;
Promise.all(rows.map((item) => (
getChoices(item.id)
.then((choices) => ({ ...item, choices }))
)))
.then((response) => {
res.send(response);
})
.catch((err) => {
// handle errors
})
});
function getChoices(questionId) {
const sql='SELECT id,text FROM `question_choices` where question_id=?';
return new Promise((resolve, reject) => {
con.query(sql,[questionId],(err,rows,fields)=>{
if(err) reject(err);
else resolve(rows);
});
});
}

Related

Express js deadlock in promise inside for loop

I have an application with node.js and expressjs and now I have a problem when I tried to insert several records into the database.
I have an array with 4 records and the applications insert 3 and the last one returns me a deadlock error from SQL.
I think that problem could be because of the promise inside the for a loop.
Next you have a resume of the code:
processOrder: (req, res) => {
//Collecting data for variables
//Promise to wait for data
Promise.all([data]).then(function(result)
{
//iterate an array
for (var i = 0; i < result[0].length; i++)
{
var new_record = request.query("INSERT INTO table_test (name, local, date) VALUES ('"+result[0][i].name+"', '"+result[0][i].local+"', '"+result[0][i].date+"')");
//Promise to wait for insert
Promise.all([ new_record]).then(function(result)
{
console.log("Record number "+i+" inserted");
}).catch(function(err) {
console.log(err);
console.log("Erro collecting data");
});
}
console.log("END");
res.send('true');
}).catch(function(err) {
console.log(err);
console.log("Erro collecting data");
});
}
The output is:
END
Record number 1 inserted
Record number 2 inserted
Record number 3 inserted
deadlock error
The "END" console.log should be the last log and not the first.
How can I solve this situation? Please explain to me what I'm doing wrong.
Thank you
I rewrote your code below.
You can try.
import { request } from "http";
processOrder: (req, res) => {
//Collecting data for variables
//Promise to wait for data
Promise.all([data])
.then(function(result){
const requests = result[0].map(record => {
// assume request.query returns promise
return request.query("INSERT INTO table_test (name, local, date) VALUES ('"+record.name+"', '"+record.local+"', '"+record.date+"')");
})
Promise.all(requests)
.then(function(result){
console.log("END");
res.send('true');
})
.catch(function(err) {
console.log(err);
console.log("Erro collecting data");
});
})
.catch(function(err) {
console.log(err);
console.log("Erro collecting data");
});
}
If you want to log "Record Number X inserted", then
make promised function which execute request.query and console.log.
function requestPromise(queryStmt, index){
return new Promise(resolve, reject){
request.query(queryStmt)
.then(result => {
console.log(`Record Number ${index} inserted`);
resolve(result);
})
.catch(err){
reject(err);
})
}
}
and then call above function like this
Promise.all([data])
.then(function(result){
const requests = result[0].map((record, index) => {
return requestPromise("INSERT INTO table_test (name, local, date) VALUES ('"+record.name+"', '"+record.local+"', '"+record.date+"')", index);
})
You are doing asynchronus stuff inside your for-loop (your query). The output of "END" is outside of the loop. You are starting 4 Promises, but you are nowhere waiting for them to resolve. So the end is printed first because the for-loop is finished as soon as the Promises are created (not really finished, but javascript contnious with the next command).
You could await the Promise inside your for loop to resolve this.
Instead of
Promise.all([ new_record]).then(function(result)
you can use
const result = await new_record;
Therefore you have to mark the whole function as async

How to implement nested loop with promise map

I have code like this:
Promise.all(venue.map(venue => {
return Promise.all(concat_all.map(tgl => {
pool.query("INSERT INTO peminjaman_venue VALUES (?,?,?,?,?,?,?,?,?,?,?)",
[id_event, venue, nama_lengkap_peminjam, jabatan_nim_peminjam, jumlah_personel,
id_google_calendar, waktu_mulai_rutin, waktu_selesai_rutin, tgl,
tgl, fasilitas_lain],
function (err, rows, fields) {
if (err) throw err;
})
}))
}).then(
req.flash('message_success', 'Berhasil mengajukan event'),
res.redirect('/pengajuan_event'))
.catch(
req.flash('message_err', 'Gagal mengajukan event'),
res.redirect('/pengajuan_event')
))
The code returns error Can't set header after they are sent, that indicates the res.redirect() is called multiple times. But the code works. The data inserted to the db successfully. I changed the code below and the code just doesnt work at all.
Promise.all(venue.map(venue => {
return Promise.all(concat_all.map(tgl => {
pool.query("INSERT INTO peminjaman_venue VALUES (?,?,?,?,?,?,?,?,?,?,?)",
[id_event, venue, nama_lengkap_peminjam, jabatan_nim_peminjam, jumlah_personel,
id_google_calendar, waktu_mulai_rutin, waktu_selesai_rutin, tgl,
tgl, fasilitas_lain],
function (err, rows, fields) {
if (err) throw err;
})
}))
}).then(() = >{
req.flash('message_success', 'Berhasil mengajukan event')
res.redirect('/pengajuan_event'))
}
.catch((err) => {
req.flash('message_err', 'Gagal mengajukan event')
res.redirect('/pengajuan_event')
}
)
)
I would create an array to hold all your async requests. So
const promises = [];
promises.push(async request 1);
promises.push(async request 2);
...
Promise.all(promises).then(result => {
// do something ...
})
I would also refactor the code a bit to use async/await to remove some of those brackets. It is hard to read with all those nested promises.

From where should I call module.exports to get a not null value?

Where should I call module.export, I assume, it's supposed to be a callback function.
But I'm confused as to where am I supposed to call the callback function.
I'm still confused with the solution, too complicated for me.
sql.connect(config, function(err) {
if (err)
console.log(err);
// create Request object
var request = new sql.Request();
// query to the database and get the records
request.query('select part_num,qty from CRM.CRM.Fishbowl_Inventory where not location = \'Shipping\'',
function(err, recordset) {
if (err)
console.log(err)
// send records as a response
var details = recordset;
});
});
module.exports = details;
Confusion:
Extremely sorry to bother you guys but I want to be sure that I'm doing no harm to our database by involving any database request through Javascript.
I'm testing directly with our production database, hence cautious
So as Max provided in his answer the following code
const connectToSql = require('./connectToSql');
connectToSql()
.then(details => {
console.log(details);
//Here I can do as much logic as I want
//And it won't affect my database or call multiple requests on my DB
})
.catch(err => {
console.log(err);
});
I can understand I'm asking super silly questions, very sorry about that.
You can't export the result of your function. You want to export a function that will return your value. Like this:
function connectToSql(config) {
return new Promise((resolve, reject) => {
sql.connect(config, function (err) {
if (err) {
console.log(err);
reject(err);
}
// create Request object
var request = new sql.Request();
// query to the database and get the records
request.query('select part_num,qty from CRM.CRM.Fishbowl_Inventory where not location = \'Shipping\'',
function (requestErr, recordset) {
if (err) {
console.log(requestErr);
reject(requestErr);
}
resolve(recordset);
});
});
});
}
module.exports = connectToSql;
Because your function is async, I returned a promise that will return your result. Also, your second error from your query is named the same as your first error from the connection. That would cause problems.
Example of how to use this:
const connectToSql = require('./connectToSql');
connectToSql()
.then(details => {
console.log(details);
})
.catch(err => {
console.log(err);
});

Promise resolves before forEach loop completes

I'm building my first CRUD (library) application and recently learned about promises as a means to avoid deeply-nested callbacks. I'm attempting to seed my DB with some data each time the server starts, but I seem to be missing something conceptually.
I have four objects in a bookData array that I want to iterate over and save to the DB using Mongoose:
function seedBooks() {
return new Promise(function(resolve,reject){
bookData.forEach(function(seed){
Book.create(seed, function(err, newBook){
if(err) {
reject(err);
}
});
});
resolve();
});
}
This function is one of a few that I am attempting to chain together, which is why I'm using promises. But I'm finding that seedBooks() resolves with anywhere between 1 and 4 of the sample books created,
function seedDB() {
removeAllBooks()
.then(function(){
return removeAllUsers();
})
.then(function(){
return seedUsers();
})
.then(function(){
return seedBooks();
})
.then(function(){
return seedBookInstances();
});
}
Am I understanding or using promises & resolve incorrectly? Any help is appreciated. Thanks!
Edit: The below explains why your code is not working and advice for, in general, how to convert non-promise code to promises. Since Mongoose produces promises, however, you should be using those instead of using new Promise. See Olegzandr's answer regarding that.
The promise is resolving immediately because you are calling resolve() immediately.
The rule of thumb when converting non-promises to promises is to promisify the smallest part of the non-promise code. In this case, it means promisifying the code to save a single item. If you do that, the collect place to call resolve() becomes clear:
function seedBook(seed) {
return new Promise(function (resolve, reject) {
Book.create(seed, function (err, newBook) {
if (err) { reject(err); } else { resolve(newBook); }
});
});
}
function seedBooks() {
return Promise.all(bookData.map(seedBook));
}
This also has the benefit of allowing you to access the returned newBooks, should you want to.
If you are using Mongoose, you can just do this:
const saveBooks = function(books) {
return books.map(function(seed) {
return Book.create(seed); // returns a promise
});
});
}
return Promise.all(saveBooks(books)).then(function(){
// all books are saved
});
You are resolving your Promise synchronously, right after you started your forEached requests, which are async. You may try the following approach instead:
function seedBooks() {
return new Promise(function(resolve,reject){
var count = 0, length = bookData.length;
bookData.forEach(function(seed){
Book.create(seed, function(err, newBook){
if(err) {
reject(err);
return;
}
if(count++ >= length ) {
resolve();
}
});
});
});
}
Here the Promise is being resolved only after all async requests are done.
Another option would be just to use Promise.all. In that approach you need to promisify all your requests in the loop, return an array of Promises and then call Promise.all(_seedBooks()).then(), where _seedBook returns an array of Promises:
function _seedBooks() {
return bookData.map(function(seed) {
return new Promise(function(resolve, reject) {
Book.create(seed, function(err, newBook) {
if(err) {
reject(err);
return;
}
resolve(newBook);
});
});
});
}
Promise.all(_seedBooks())
.then(function(result) { /* result is the array of newBook objects */ })
.catch(function(error) { /* error is the first rejected err */ })

What the right way to use Promises with mongoose?

I'm new with Promises and in two days a already saw at least five ways to do.
Some ways are older versions, others focus on other languages, so I decide to ask what should be used today.
I did it this way, with 'err' and 'doc' parameters:
Proposals.findById({ ...body, user }, (err, doc) =>
Advertisements.findOneAndUpdate(
{_id: body.advertisement}, {$push: {proposals: doc._id}}
)
)
.then((Proposals) => Proposals.view())
.then(success(res, 201))
.catch(next)
And this:
Proposals.create({ ...body, user })
.then((Proposals) => {
Advertisements.findOneAndUpdate(
{_id: body.advertisement}, {$push: {proposals: Proposals._id}}
)
.catch(Advertisements)
return Proposals.view()
})
.then(success(res, 201))
.catch(next)
Without the "catch" and "return" itsn't work, but I don't know why exactly. :x
The answer to
Using Promises in Mongoose Routes
seems like a beautiful but I couldn't reproduce in code above.
return findUser(userId)
.then((user) => findReceipt(user.bookName))
.then((receipt) => res.status(200).json(receipt))
.catch((err) => res.status(500).json(null))
Are any of these codes more right?
Could you help me to fix the second code?
Thank you.
Let me detail the syntax of the first block of code:
Proposals.findById({ ...body, user }, (err, doc) => {
return Advertisements.findOneAndUpdate(
{_id: body.advertisement}, {$push: {proposals: doc._id}}
);
})
.then((Proposals) => { return Proposals.view() })
.then(success(res, 201))
.catch(next)
In ES6 syntax, this function:
() => ('test');
Equals to
() => { return 'test' };
So I believe your main problem is because of not understanding exactly the ES6 syntax at the beginning.
You should follow these best practices when it comes to Promises:
Avoid nesting catch in Promises
Chain Promises by returning them and use them in the then
Hope it helps.
There are number of ways to handle promises in Mongoose. The exact way that you decide to implement it is left to personal preference, but you need to make sure that your order of execution is as expected. In your first example you are mixing a callback with promises, which I personally think is not optimal. The third example is cleaner as it is a chain of promises being passed down via the then calls.
The important thing to note is that if you want to run async code inside your then calls, you need to ensure you return a Promise that resolves at the right moment. In your second example, you are possibly returning before your findOneAndUpdate call is finished executing. You can avoid this in a number of ways. One option would be to wrap the second call inside a Promise. I give you an idea below by mixing parts of your second and third examples and assuming that the return value from proposal.view() is not async. I use explicit return statements for clarity.
Proposals.create({ ...body, user })
.then((proposal) => {
// call returns a new promise
return new Promise((resolve, reject) => {
// make second async call to database
Advertisements.findOneAndUpdate(
{ _id: body.advertisement },
{ $push: { proposals: proposal._id } },
)
.then(() => {
// promise resolves here and passes return
// value of proposal.view() to next `then`
return resolve(proposal.view())
});
});
})
// consume the value from proposal.view() and send it in response
.then(proposal => res.status(200).json(proposal))
.catch(next)
This would mean that the return value of proposal.view() will only be passed onto the next then once the findOneAndUpdate call is complete.
The example code is not waiting for, or using the result of the update.
In promises returning a promise and using .then waits for that async operation. You can then check the result if required:
Proposals.create({ ...body, user })
.then((proposal) => {
return Advertisements.findOneAndUpdate(
{_id: body.advertisement}, {$push: {proposals: proposal._id}}
)
})
.then((update) => {
if (!update) throw new Error(`Could not find advertisement for "${user}"`)
success(res, 201))
})
.catch(next)
Or the equivalent ES2017 code using await to wait for the promise to resolve, which should help reason how the above Promise .then code flow works.
async function handler(){
try {
let proposal = await Proposals.create({ ...body, user })
let update = await Advertisements.findOneAndUpdate(
{_id: body.advertisement}, {$push: {proposals: proposal._id}}
)
if (!update) throw new Error(`Could not find advertisement for "${user}"`)
success(res, 201)
}
catch (err) {
next(err)
}
}

Categories

Resources