#Sequelize findAll events and forEach event getUser - javascript

I got some problems with Sequelize and promise (mainly with promise).
Example :
I want all my Events and the master of this events. I've done something like that :
models.events.findAll().then(function(event) {
events.forEach(function(event){
events.dataValues.master = event.getUsers({
where: ['UserRelationEvent.relation = "master"'],
joinTableAttributes: []
}).then(function(user){
return user[0].dataValues;
});
});
return next(events);
}).catch(function(err) {next(err)});
But there I got an Sequelize Object.. I checked my content of user[0].dataValues It's exactly what I want.. so I think I miss something and misunderstood something with promises :/
I try many things but mainly my question is : how can I retrieve my string from my console.log

event.getUsers returns a promise - so you are assigning events.dataValues.master (which is the array, not the particular event btw) the promise, not the value.
Also, you are returning next, before getUsers is done, because they happen async
Something like this should work
models.events.findAll().then(function(events) {
sequelize.Promise.each(events, function(event){
return event.getUsers({
through: {
where: { relation: 'master' }
},
joinTableAttributes: []
}).then(function(user){
event.dataValues.master = user[0];
});
}).then(function (events) {
return next(events);
});
}).catch(function(err) {next(err);});
When you return a promise to the callback in promise.each, the then (which calls next) is not invoked, before all the returned promises are done (i.e. before all getUsers calls are done.
I've also changed the where clause to use an object instead of string :)
But wait! We can do better!
models.events.findAll({
include: [
{
model: User,
through: {
where: {
relation: 'master'
}
}
}
]
}).then(function(events) {
return next(events);
}).catch(function(err) {next(err);});
This left joins the users table where the relation is master. You may want to do a map in the then, because users will be placed under .users, not .master

Related

Multiple Mongoose Calls Within a For Each Loop

I am reading a JSON object and looping through each item. I am first checking to see if the item already exists in the database and if so I want to log a message. If it doesn't already exist I want to add it.
This is working correctly however, I would like to add a callback or finish the process with process.exit();
Because the mongoose calls are asynchronous I can't put it at the end of the for loop because they haven't finished.
Whats the best way I should handle this?
function storeBeer(data) {
data.forEach((beer) => {
let beerModel = new Beer({
beer_id: beer.id,
name: beer.name,
image_url: beer.image_url
});
Beer.findOne({
'name': beer.name
}).then(function (result) {
if (result) {
console.log(`Duplicate ${result.name}`)
} else {
beerModel.save(function (err, result) {
console.log(`Saved: ${result.name}`)
});
}
});
});
}
Is there anything I should read up on to help solve this?
One means of managing asynchronous resources is through Promise objects, which are first-class objects in Javascript. This means that they can be organized in Arrays and operated on like any other object. So, assuming you want to store these beers in parallel like the question implies, you can do something like the following:
function storeBeer(data) {
// This creates an array of Promise objects, which can be
// executed in parallel.
const promises = data.map((beer) => {
let beerModel = new Beer({
beer_id: beer.id,
name: beer.name,
image_url: beer.image_url
});
return Beer.findOne({
'name': beer.name
}).then(function (result) {
if (result) {
console.log(`Duplicate ${result.name}`)
} else {
beerModel.save(function (err, result) {
console.log(`Saved: ${result.name}`)
});
}
});
);
});
return Promise.all(promises);
}
Don't forget that the storeBeer function is now asynchronous, and will need to be utilized either through a Promise chain or through async/await.
For example, to add process exit afterwards:
async function main() {
const beers = [ ... ];
await storeBeer(beer);
process.exit(0);
}
You can also modify the above example to invoke the storeBeer function within a try / catch block to exit with a different error code based on any thrown errors.

Can't call object properly with promises

My code was working fine until I decided I wanted to try and return two values in my promise chain to the next function. I can call one of the values but not the other.
My code looks like this
app.get('/projects', (req, res) => {
practice.screamIt('matt').then((name) => {
return [practice.translateIt(name), name]; //puts name as the parameter for next function
}).then((translate) => {
console.log(translate[1] + ' test occured here')
console.log(translate[0][0].englishName)
return [`The name you entered is ${translate[1]}`, `${translate[0].englishName} is ${translate[0].spanishName} in spanish`]
}).then((value)=>{
res.render('projects', {
pageTitle: "Projects Page",
practice: practice,
value1: value[0],
value2: value[1]
});
}).catch((errorMessage)=>{
console.log(errorMessage)
})
});
And when I log the first bit of data it shows:
[ Promise { { englishName: 'Matt', spanishName: 'Mateo' } },'Matt' ]
I want to be able to call englishName, but can't seem to do so without getting undefined. I need to be able to call englishName in order for my second function work as intended.
You need to first resolve the translateIt() promise and use another then() to create the array
Change:
return [practice.translateIt(name), name];
To
return practice.translateIt(name).then(translate => [translate, name]);
I don't have a full copy if your code but it appears you're returning a Promise in an Array to the then handler which I don't believe will work?
practice.screamIt('matt').then((name) => {
return practice.translateIt(name);
}).then((translate) => {
...etc
}).catch((errorMessage)=>{
console.log(errorMessage)
});
If you try something like that I believe that would work for you as it would be returning the result once the promise resolves.
I suppose you need to return Promise.all([practice.translateIt(name), name]) instead of practice.translateIt(name) as I its returning a promise.
That should return a single promise resolving to an array of values.

Nesting API calls in React Redux

I am having difficulty figuring out what is happening (and not happening) in my action creator.
I need to make a call to one API endpoint, get the ids and names of all the items returned, then for each of those ids, make another call. I want to store the return of the last call and the ids/names from the first call in an object and dispatch it.
{
category: name //name of category
subcategory: [] //array of categories in the category above.
}
Right now, my reducer does end up having what I want, but when I attempt to log that particular prop in the component it is empty. (below I am using OpenSocialAPI or osapi. This is just a basic wrapper for an ajax request. Allows for me to not have to authenticate as it sees I am already authenticated.)
export function fetchCategories(id){
let categories = []
return function(dispatch){
dispatch(requestCategories(id))
return osapi.core.get({
v: "v3",
href: "/places/" + id + "/places"
}).execute(function(response) {
response.content.list.forEach(function(category) {
osapi.core.get({
v: "v3",
href: "/places/" + category.id+ "/categories"
}).execute(function(response) {
categories.push({
category: category.name,
subcategories: response.content.list.map(category => category.name)
})
})
})
console.log("Category response: ", categories)
dispatch(receiveCategories(id, categories))
})
}
}
export function receiveCategories(id,array){
return {
type: RECEIVE_CATEGORIES,
id,
categories: array,
recievedAt: new Date(Date.now()),
isFetching: false
}
}
And in my app I am dispatching the action creator in componentDidMount
componentDidMount() {
const { dispatch } = this.props
dispatch(fetchCategoriesIfNeeded(id))
}
Right now when I console log in my Category component and in the execute above, it is empty. But looking at my state in my logger, when recieveCategories is completed, I have the array of objects I want
[{category:...,
subcategories:[...]
},
{category:...,
subcategories:[...]
}]
I suspect this is because of something asynchronous but I'm unsure how to proceed.
I attempted to create my own wrapper for the call that was promise based, but I had similar issues, probably more so because I'm not sure if resolve(response) is what I want.
function httpService(path){
return new Promise(function(resolve, reject){
osapi.core.get({
v: 'v3',
href: path
}).execute(function(response, error){
if(error){
return reject(new Error("Error: ", error))
}
resolve(response)
})
})
}
export function fetchCategories(spaceId) {
let categories = []
return function(dispatch) {
dispatch(requestCategories(id))
return httpService("/places/" + id + "/places")
.then(function(response) {
response.content.list.forEach(function(category) {
fetchSubCategories("/places/" + category.id + "/categories")
.then(function(response) {
categories.push({
category: category.name,
subcategories: response
})
})
})
console.log("CATEGORIES: ", categories)
dispatch(receiveCategories(id, categories))
})
}
}
function fetchSubCategories(url){
return httpService(url)
.then(function(response){
return response.content.list.map(category => category.name)
})
}
Can you look at this and give guidance? Also, is me dispatching an array that I built based on the API responses a valid way of doing things or is there someway better? Thank you
I was only able to find 1 other question with similar use case but they are using bluebird or something similar. I'd really like to keep this without anything extra besides Redux.
It looks like you just need to dispatch your categories inside your .execute() callback, not outside of it. You're doing osapi.core.get().execute((response) => but then outside of that execute callback, you dispatch receiveCategories, which will execute long before your Promise resolves, and dispatch the empty array you initialize.
You also need to use Promise.all to get the response of all of your nested GET requests.
There's also no reason to keep a mutating array around.
I guess osapi.core.get is some kind of promise based fetch library? And .execute is called when the get succeeds?
If so, then what you're missing is that you're not waiting for all asynchronous calls to finish.
I'm going to show a solution based on generic fetch and native Promises so you can understand the solution and adopt it based on your specific libraries.
const promises = [];
response.content.list.forEach(function(category) {
const promise = fetch("/places/" + category.id+ "/categories");
promises.push(promise);
})
Promise.all(promises).then(responses => {
categories = responses.map(response => ({/* do your object mapping here */}))
// now you can dispatch with all received categories
dispatch(receiveCategories(id, categories))
});
Also, you're using the same variable in your nested functions - while this may work and the computers may understand it, it makes it super hard for any human to figure out which response belongs to which scope. So you may want to take a second look at your variable names as well.

Holding a future value of a promise in a variable

I have a database that's calling for a list of recent messages. Each message is an object and is stored as an Array of these message objects in chatListNew.
Each message object has a property "from", which is the ID of the user who posted it. What I want to do, is loop through this Array and append the actual profile information of the "From" user into the object itself. That way when the Frontend receives the information, it has access to one specific message's sender's profile in that respective message's fromProfile property.
I thought about looping through each one and doing a Promise.All for every one, however, that's hugely expensive if only a handful over users posted hundreds of messages. It would make more sense to only run the mongoose query once for each user. So I invented a caching system.
However, I'm confused as to how to store the promise of a future value inside of an array element. I thought setting the "fromProfile" to the previously called promise would magically hold this promise until the value was resolved. So I used Promise.all to make sure all the promises were completed and then returned by results, but the promises I had stored in the arrays were not the values I had hoped for.
Here is my code:
//chatListNew = an array of objects, each object is a message that has a "from" property indicating the person-who-sent-the-message's user ID
let cacheProfilesPromises = []; // this will my basic array of the promises called in the upcoming foreach loop, made for Promise.all
let cacheProfilesKey = {}; // this will be a Key => Value pair, where the key is the message's "From" Id, and the value is the promise retrieving that profile
let cacheProfileIDs = []; // this another Key => Value pair, which basically stores to see if a certain "From" Id has already been called, so that we can not call another expensive mongoose query
chatListNew.forEach((message, index) => {
if(!cacheProfileIDs[message.from]) { // test to see if this user has already been iterated, if not
let thisSearch = User.findOne({_id : message.from}).select('name nickname phone avatar').exec().then(results => {return results}).catch(err => { console.log(err); return '???' ; }); // Profile retrieving promise
cacheProfilesKey[message.from] = thisSearch;
cacheProfilesPromises.push(thisSearch); // creating the Array of promises
cacheProfileIDs[message.from] = true;
}
chatListNew[index]["fromProfile"] = cacheProfilesKey[message.from]; // Attaching this promise (hoping it will become a value once promise is resolved) to the new property "fromProfile"
});
Promise.all(cacheProfilesPromises).then(_=>{ // Are all promises done?
console.log('Chat List New: ', chatListNew);
res.send(chatListNew);
});
And this is my console output:
Chat List New: [ { _id: '5b76337ceccfa2bdb7ff35b5',
updatedAt: '2018-08-18T19:50:53.105Z',
createdAt: '2018-08-18T19:50:53.105Z',
from: '5b74c1691d21ce5d9a7ba755',
conversation: '5b761cf1eccfa2bdb7ff2b8a',
type: 'msg',
content: 'Hey everyone!',
fromProfile:
Promise { emitter: [EventEmitter], emitted: [Object], ended: true } },
{ _id: '5b78712deccfa2bdb7009d1d',
updatedAt: '2018-08-18T19:41:29.763Z',
createdAt: '2018-08-18T19:41:29.763Z',
from: '5b74c1691d21ce5d9a7ba755',
conversation: '5b761cf1eccfa2bdb7ff2b8a',
type: 'msg',
content: 'Yo!',
fromProfile:
Promise { emitter: [EventEmitter], emitted: [Object], ended: true } } ]
Whereas I was hoping for something like:
Chat List New: [ { _id: '5b76337ceccfa2bdb7ff35b5',
updatedAt: '2018-08-18T19:50:53.105Z',
createdAt: '2018-08-18T19:50:53.105Z',
from: '5b74c1691d21ce5d9a7ba755',
conversation: '5b761cf1eccfa2bdb7ff2b8a',
type: 'msg',
content: 'Hey everyone!',
fromProfile:
Promise {name: xxx, nickname: abc... etc} },
{ _id: '5b78712deccfa2bdb7009d1d',
updatedAt: '2018-08-18T19:41:29.763Z',
createdAt: '2018-08-18T19:41:29.763Z',
from: '5b74c1691d21ce5d9a7ba755',
conversation: '5b761cf1eccfa2bdb7ff2b8a',
type: 'msg',
content: 'Yo!',
fromProfile:
{name: xxx, nickname: abc... etc} } ]
Thank you guys! Open to other ways of accomplishing this :)
Pete
When a Promise is assigned to a variable, that variable will always be a Promise, unless the variable is reassigned. You need to get the results of your Promises from your Promise.all call.
There's also no point to a .then that simply returns its argument, as with your .then(results => {return results}) - you can leave that off entirely, it doesn't do anything.
Construct the array of Promises, and also construct an array of from properties, such that each Promise's from corresponds to the item in the other array at the same index. That way, once the Promise.all completes, you can transform the array of resolved values into an object indexed by from, after which you can iterate over the chatListNew and assign the resolved value to the fromProfile property of each message:
const cacheProfilesPromises = [];
const messagesFrom = [];
chatListNew.forEach((message, index) => {
const { from } = message;
if(messagesFrom.includes(from)) return;
messagesFrom.push(from);
const thisSearch = User.findOne({_id : from})
.select('name nickname phone avatar')
.exec()
.catch(err => { console.log(err); return '???' ; });
cacheProfilesPromises.push(thisSearch);
});
Promise.all(cacheProfilesPromises)
.then((newInfoArr) => {
// Transform the array of Promises into an object indexed by `from`:
const newInfoByFrom = newInfoArr.reduce((a, newInfo, i) => {
a[messagesFrom[i]] = newInfo;
return a;
}, {});
// Iterate over `chatListNew` and assign the *resolved* values:
chatListNew.forEach((message) => {
message.fromProfile = newInfoByFrom[message.from];
});
});
A Promise is an object container, like a Array. The difference being that a Promise holds a value that will sometimes be.
So, since you do not know when the value will be resolved in Promise jargon, generally you tell the promise what to do with the value, when it is resolved.
So for example,
function (id) {
const cache = {}
const promise = expensiveQuery(id)
// promise will always be a promise no matter what
promise.then(value => cache[id] = value)
// After the callback inside then is executed,
// cache has the value you are looking for,
// But the following line will not give you the value
return cache[params.id]
}
Now, what you might do to fix that code is, return the promise when the query is run for the first time, or return the cached value.
// I moved this out of the function scope to make it a closure
// so the cache is the same across function calls
const cache = {}
function (id) {
if(cache[id]) return cache[id]
const promise = expensiveQuery(id)
// promise will always be a promise no matter what
promise.then(value => cache[id] = value)
// now we just return the promise, because the query
// has already run
return promise
}
Now you'll have a value or a promise depending on whether the function has already been called once before for that id, and the previous call has been resolved.
But that's a problem, because you want to have a consistent API, so lets tweak it a little.
// I moved this out of the function scope to make it a closure
// so the cache is the same across function calls
const cache = {}
function cachingQuery (id) {
if(cache[id]) return cache[id]
const promise = expensiveQuery(id)
// Now cache will hold promises and guarantees that
// the expensive query is called once per id
cache[id] = promise
return promise
}
Ok, now you always have a promise, and you only call the query once. Remember that doing promise.then doesn't perform another query, it simply uses the last result.
And now that we have a caching query function, we can solve the other problem. That is adding the result to the message list.
And also, we dont' want to have a cache that survives for too long, so the cache can't be right on the top scope. Let's wrap all this inside a cacheMaker function, it will take an expensive operation to run, and it will return a function that will cache the results of that function, based on its only argument.
function makeCacher(query) {
const cache = {}
return function (id) {
if(cache[id]) return cache[id]
const promise = query(id)
cache[id] = promise
return promise
}
}
Now we can try to solve the other problem, which is, assign the user to each message.
const queryUser = makeCacher((id) => User.findOne({_id : id})
.select('name nickname phone avatar')
.exec())
const fromUsers = chatListNew.map((message) => queryUser(message.from))
Promise.all(fromUsers)
.then(users =>
chatListNew.map(message =>
Object.assign(
{},
message,
{ fromProfile: users.find(x => x._id === message.from)})))
.then(messagesWitUser => res.json(messagesWitUser) )
.catch(next) // send to error handler in express

Promise on addAssociation is resolved with a stale version of the source

I have a versioning schema that versions object entries in a object_version table
const Object = sequelize.define('object', {})
const ObjectVersion = sequelize.define('object_version', {
title: {
allowNull: false,
type: DataTypes.STRING
}
})
Object.hasMany(ObjectVersion, { as: 'versions' })
Using express, i have a put route where an existing object entry can be updated, in this simple example by setting a new title, e.g. by sending a PUT request with a body of {"title":"new title"}
app.put('object/:id', (req, res) => {
const id = parseInt(req.params.id, 10)
Object.findOne({
where: { id },
include: [ Object.associations.versions ]
}).then(object => {
const newVersion = ObjectVersion.build(req.body, { objectId: id })
Object.addVersion(newVersion).then(object => {
// shouldn't this instance have all versions, including the new one?
// do i need to `findOne` again to get them?
console.log(object.toJSON().versions)
res.send(object)
})
}
})
})
When using the addVersion method that is created on my Object model by associating it to the ObjectVersion model via hasMany, the returned promise is resolved with an object instance.
The problem is that i want to return the objects JSON in the response, but the object instance the promise is resolved with does not contain the version that was just added.
To me, this looks like an oversight. It should not be necessary to perform another query. When the promise for the addXyz operation is resolved, the new association is already saved in the database so it should be possible to resolve the promise with an updated version of the object.
Maybe i'm going at this the wrong way or missed an easier way to perform an update operation that adds new associations. Any help is welcome!
After an interesting talk on Slack i realised that the addAssociation, addAssociations and setAssociations methods on model instances connect existing rows in the db with the target.
In my example, the addVersion method connects an existing object_version row with the current version instance and can not be used to insert a new row into the database.
I've opened a PR to sequelize trying to clarify this in the docs as i skipped right over this. An example that works might look like this:
app.put('object/:id', (req, res) => {
const id = parseInt(req.params.id, 10)
Object.findOne({
where: { id },
include: [ Object.associations.versions ]
}).then(object => {
object.createVersion(req.body).then(version => {
object.versions.push(version)
res.send(object)
})
})
})

Categories

Resources