I'm trying to reload a model in order to include association. This is my code:
const order = new Order({ total: 0 })
return order.save().then(savedOrder => {
const orderItems = req.body.items.map(i => {
return Eliquid.findByPk(i.eliquid).then(eliquid => {
const item = Item.build({
qty: i.qty,
n: i.n,
})
item.setEliquid(eliquid)
item.setOrder(savedOrder)
return item
.save().then(i => {
console.log('Saved, has id, has qty', i.id, i.qty)
return i.reload()
})
.then(i => {
console.log('Reloaded, qty is now NULL!', i.id, i.qty)
return i
})
})
})
Why after reloading my instance it gets whiped out? Don't know what I'm doing wrong
As far as I can tell, .reload() (unlike .save()) doesn't deliver the item back to the client; instead, it synchronises the original item's properties with those on the server.
I'm not sure you actually need to reload in this case because client & server representations of the item will automatically be in sync immediately after .save(), but here's an example of how you would exploit javascript's "closure" to maintain access to the item, after .reload().
const order = new Order({ 'total': 0 });
return order.save()
.then(savedOrder => {
const orderItems = req.body.items.map(i => {
return Eliquid.findByPk(i.eliquid)
.then(eliquid => {
const item = Item.build({ 'qty': i.qty, 'n': i.n });
item.setEliquid(eliquid);
item.setOrder(savedOrder);
return item.save();
})
.then(ii => {
console.log('Saved, has id, has qty', ii.id, ii.qty);
return ii.reload()
.then(() => { // .reload() does not repeat item back to client
console.log('same ii as before', ii.id, ii.qty); // here, access ii through closure.
return ii;
});
});
});
});
Notes:
for clarity ii is used in the second .then() callback to avoid reusing the .map() callback's symbol i.
the inner promise chain is flattened as far as possible. If you needed access to eliquid in the second inner .then(), you would need to revert to return item.save().then() and incur a futher level of nesting.
in addition to exploiting closure, other approaches exit - see the comprehensive answers here.
Related
This question already has an answer here:
Returning value from async function node.js
(1 answer)
Closed 1 year ago.
I'm looking to push object from a forEarch, after getting data inside forEarch, the console.log show me correct value and object are properly pushed to the array.
But at the end of the forEach, if I try to access to the object pushed, there is no object inside the array
Code below
async function get_line_items(items) {
line_items2 = [];
await items.forEach((element, index) => {
const id = element.id;
if (id) {
await getPriceFromCartCheckout(id, (err, results) => {
if (err) {
console.log(err);
return;
}
if (results && !isEmpty(results)) {
// Add new item
line_items2.push({
name: element.name,
amount: results[0].price,
currency: "eur",
quantity: element.quantity,
})
}
console.log(line_items2) // => Here objects are properly added to the array on each loop
});
}
})
console.log(line_items2) // => Here the array is empty.
return line_items2;
}
const line_items = await get_line_items(req.body[1].items);
console.log( line_items); // => Here the array is empty.
module.exports = {
getPriceFromCartCheckout: async (id) => {
return await pool.query(`SELECT volume, price FROM products WHERE id = ?`, [
parseInt(id)
])
.then(iuidtest => {
return iuidtest;
});
},
};
Any help would be appreciate :)
Found the solution here :
NodeJS async MySQL call to connection pool returns no result
You should await getProductIdWeightPrice and not the loop itself. The loop does what it's supposed to do - looping the array, calling some external methods. What those methods do - the loop does not care.
In order to "pause" the loop, you need to await the async call to get the data. Do that and it will work :)
I'm retrieving a list of people from the database using getPeople(). As soon as I receive them as res, I want to prepare them to be stored in my local mongodb if they do not exist. Sometimes there're duplicate entries (for one id) within res. My issues is that it's not waiting for Person.create(pers) to finish, continues searching if this id is already in mongodb, can't find any since Person.create(pers) is still creating it and starts the second Person.create(pers)..
this.getPeople()
.then(res => {
return Promise.all(res.map(pers => {
pers.birthday = df(pers.birthday, 'dd.mm.yyyy')
pers.pickedUp = false
console.log(pers.id)
return Person
.find({ id: pers.id })
.exec()
.then(found => {
if (found === undefined || found.length == 0)
return pers
})
.then(pers => {
return Person
.create(pers)
.then(console.log('created'))
.catch(err => console.log(err))
})
}))
}).catch(err => console.log(err))
I expected the console output to be like this:
940191
created
940191
created
Instead, I'm getting this:
940191
940191
created
created
That's because Promise.all simply awaits all the promises you're mapping. It does not guarantee any order in which the promises are resolved.
If you want to sequentially process the elements of your res-array, you can simply use a for .. of loop in combination with async/await (note that this still needs some error handling, but it should give you something to start with):
async function getPeopleAndCreateIfNotExisting() {
const persons = [];
const res = await this.getPeople();
for (const pers of res) {
pers.birthday = df(pers.birthday, 'dd.mm.yyyy');
pers.pickedUp = false;
console.log(pers.id)
const found = await Person
.find({ id: pers.id }).exec();
if (found) {
persons.push(pers);
} else {
persons.push(await Person.create(pers));
}
}
return person;
}
I have an async function (createObjects) that needs to create some models into the database, so, until all the objects are created (inside a forEach loop), the function needs to wait. After the last model is created, then it should return the "Data Synchronized!" string, but the "Data Synchronized!" message never waits for createObjects() to finish. I think I need to return all the model.create promises to the mainPromise, like an array of promises, but I don't know the best way to do that. Any suggestions?
PS: The calculateCost that is called inside createObjects is async and is working fine.
mainPromise()
.then( (data) => {
return proccessData(data); //this is a sync function
})
.then( (newData) => {
createObjects(newData); // this is a async function
})
.then( () => {
return "Data Synchronized!";
})
//this needs to be an async function
function createObjects(newData){
newData.forEach((bills) => {
//if the object bills has the Groups array attributes...
if (bills.Groups.length !== 0) {
//iterate through groups
bills.Groups.forEach( (group) => {
var uid = group.id;
var usage = group.Metric.UsageAmount;
var cost = calculateCost(usage, uid); //async function
//the problem is here
cost.then((result) => {
models.Billing.create({
group_id: uid,
cost: result,
usage: usage
});
});
})
}
});
}
var calculateCost = (usage, uid) => {
var cost;
return models.ObjectA.findOne({
where: { id: uid }
}).then((data) => {
if (data.type == "Interactive") {
cost = usage * 0.44;
} else if (data.type == "Automated") {
cost = usage * 0.11;
}
return cost;
});
}
There is nothing in your code watching the result of cost().then(...), so that bit of code is fire-and-forget. The same is true for your call to models.Billing.create and one of the thens toward the top of your code. That's why you're seeing the outcome you are. When using Promises, be on the lookout for places where you are creating promises and not returning them to a higher caller. This often suggests the creation of a promise that isn't being watched.
To fix this:
First of all, fix the then towards the top of the code so that the result of createObjects is actually being returned:
.then( (newData) => {
return createObjects(newData); // this is a async function
})
Better yet:
.then(createObjects)
After that's remedied...
Option 1 - use reduce instead of forEach
Use this approach if you want to assure that the queries are executed one at a time (in sequence), instead of all at once:
function processBillGroups(groups) {
return groups.reduce((last, group) => {
var group_id = group.id;
var usage = group.Metric.UsageAmount;
return last
.then(() => calculateCost(usage, group_id))
.then((cost) => models.Billing.create({ group_id, cost, usage }))
}, Promise.resolve());
}
function createObjects(newData) {
return newData.reduce(
(last, { Groups }) => last.then(() => processBillGroups(Groups)),
Promise.resolve(),
);
}
Option 1.1 Use async/await
This will also carry out the actions in sequence, but uses the async/await syntax instead of direct promise manipulation.
async function processBillGroups(groups) {
for (group of groups) {
let group_id = group.id;
let usage = group.Metric.UsageAmount;
let cost = await calculateCost(usage, group_id);
await models.Billing.create({ group_id, cost, usage });
}
}
async function createObjects(newData) {
for ({ Groups } of newData) {
await processBillGroups(Groups);
}
}
Option 2 - use map and Promise.all instead of forEach
Use this if you don't mind all of the actions executing at the same time (in parallel), or even prefer that they execute in parallel. createObjects will return a single promise that will resolve when all of the actions have completed:
function processBillGroups(groups) {
return Promise.all(groups.map((group) => {
var group_id = group.id;
var usage = group.Metric.UsageAmount;
return calculateCost(usage, group_id)
.then((cost) => models.Billing.create({ group_id, cost, usage }));
}));
}
function createObjects(newData) {
return Promise.all(
newData.map(({ Groups }) => processBillGroups(Groups))
);
}
Option 2.1 - Use map and Promise.all with a little bit of async/await:
Acts just like option 2, but the syntax is a little nicer.
function processBillGroups(groups) {
return Promise.all(groups.map(async (group) => {
let group_id = group.id;
let usage = group.Metric.UsageAmount;
let cost = await calculateCost(usage, group_id);
await models.Billing.create({ group_id, cost, usage });
}));
}
function createObjects(newData) {
return Promise.all(
newData.map(({ Groups }) => processBillGroups(Groups))
);
}
I want to check whether a username is already in use using pg-promise.
I use the following query:
this.db.one('SELECT EXISTS(SELECT 1 FROM users WHERE username = $1)', username);
I am trying to encapsulate this query inside a function that simply returns true if the username exists, false if not.
Something like:
existsUsername(username){
this.db.one('SELECT EXISTS(SELECT 1 FROM users WHERE username = $1)', username)
.then(data => {
if(data.exists == true){
return true;
} else {
return false;
}
});
}
That I can simply use like so:
if(db.users.existsUsername(username)){
// this username is already taken
}
However the if condition is assessed before the query finishes, resulting in an undefined variable.
What is the proper way of returning the result of a query?
EDIT: the outer caller performs multiple async checks and returns whether the user is valid or not:
function signUp(username, email, ...){
// perform username existence check existsUser(username)
// perform email existence check existsEmail(username)
// ...
// if everything OK, put user in DB
}
Simplest way of doing it:
existsUsername(username) {
return this.db.oneOrNone('SELECT * FROM users WHERE username = $1 LIMIT 1', username, a => !!a);
}
And then use it:
db.users.existsUsername(username)
.then(exists => {
// exists - boolean
})
.catch(error => {
});
You cannot do things like if(db.users.existsUsername(username)), that's mixing up synchronous code with asynchronous. But you can do if(await db.users.existsUsername(username)), if ES7 syntax is available to you.
And if you have three independent functions like that (checkUserName, checkEmail, checkWhateverElse), and want to execute them all, here's the best way to do it:
db.task(t => {
return t.batch([checkUserName(t), checkEmail(t), checkWhateverElse(t)]);
})
.then(data => {
// data = result of the batch operation;
})
.catch(error => {
// error
});
The same with ES7 syntax:
db.task(async t => {
const a = await checkUserName(t);
const b = await checkEmail(t);
const c = await checkWhateverElse(t);
return {a, b, c};
})
.then(data => {
// data = {a, b, c} object;
})
.catch(error => {
// error
});
Note: Each of those functions is expected to execute queries against t - task context, in order to share the connection.
You can't use async operation in sync way, you need to rewrite the code that checks if user exists in async way as well. I.e.:
// returns a promise
function existsUsername(username){
return this.db.one('SELECT EXISTS(SELECT 1 FROM users WHERE username = $1)', username);
}
And next use it in app in a way like
db.users.existsUsername(username)
.then( data => {
data.exists ? handleUserExistsAsync() : handleUserNotExistsAsync();
})
.catch( err => {
// some err occurs, db fail or something
// however, you can catch it in an upper level
});
EDIT: Using Promise.all for multiple tasks, may have performance and connection issues (as Vitaly mentioned).
It's better to use db.batch inside db.task
I'm trying to update a model in Sequelize using the following code:
exports.updateItem = function(item) {
return new Promise((fulfill, reject) => {
models.TimesheetItem.update(item,{where: {id: item.id}})
.then(fulfill)
.catch(console.dir);
});
};
Where item is the result of doing models.TimeSheetItem.find()
The call never executes the .then and instead passes an empty object to the .catch.
I've looked over the documentation and it seems that this is the way to update a row, but I can't get it to work. What am I doing wrong?
Thank you!
According to the documentation the update method takes two parameters - first one is values which will be used to perform the update, and second one is options - so in your case this is the where clause. If you want to perform an update on a single instance only, you can do it in two ways - use Model.update() method, which can update multiple instances of this model at once matching the where clause, or perform instance.update() in order to update only the single instance. First option will look like that:
let updateValues = { name: 'changed name' };
models.Model.update(updateValues, { where: { id: 1 } }).then((result) => {
// here your result is simply an array with number of affected rows
console.log(result);
// [ 1 ]
});
The first option is not very useful when you want to update only single instance. So that is why there is a possibility of performing update() on Sequelize model instance
let updateValues = { name: 'changed name' };
instance.update(updateValues).then((self) => {
// here self is your instance, but updated
});
In your case, if the item parameter is a Sequelize model instance (not a plain javascript JSON object), your update function could be like that
exports.updateItem = function(item){
return item.update(values).then((self) => {
return self;
}).catch(e => {
console.log(e);
});
};
However, if the item is not a sequelize model instance but only a plain object with values you want to update, it could be done in two ways - first is to use Model.update() (just like you did), or second one is to retrieve TimesheetItem with id = item.id and then perform instance.update() as shown above
exports.updateItem = function(item){
models.TimesheetItem.update(item, { where: { id: item.id } }).then((result) => {
// here result will be [ 1 ], if the id column is unique in your table
// the problem is that you can't return updated instance, you would have to retrieve it from database once again
return result;
}).catch(e => {
console.log(e);
});
};
Or the second option with returning instance and performing update on it
exports.updateItem = function(item) {
return models.TimesheetItem.findById(item.id).then((itemInstance) => {
return itemIstance.update(item).then((self) => {
return self;
});
}).catch(e => {
console.log(e);
});
}
The difference is that you do not need to create and return your own Promise - sequelize methods like update() return promises by themselves.
{ error: type "where" does not exist}
Just an update to the solution, Sequelize now gives the error above when you include the 'where' option(as shown below). So take it out and it should work perfectly.
exports.updateItem = function(item){
models.TimesheetItem.update(item, { id: item.id }).then((result) => {
// here result will be [ 1 ], if the id column is unique in your table
// the problem is that you can't return updated instance, you would have to retrieve it from database once again
return result;
}).catch(e => {
console.log(e);
});
};