i'm trying to get a list of matching entities from a querystring. i'm using s3 to store my objects.
The problem is that the promise that resolves the 'main promise' is ansych only return the array when given a setTimeOut. Else it returns [undefined, undefined...].
my code looks like this:
getEntities: (type, offset, limit, query) => {
return new Promise((resolve, reject) => {
seekKeys(type, offset, limit, query)
.then((response) => {
let entities = [];
if (response.hasBeenFiltered) { // This is not used yet.
for (let i = offset; i < response.keys.length && i < offset + limit; i++) {
entities.push(matchInstance(instance))
}
resolve(entities)
} else { // here is where my issue's at.
console.log("Keys found: " + response.keys.length)
parseQuery(type, query)
.then((conditions) => {
let matches = response.keys.map((key) => {
readEntity(key).then((instance) => {
logger.debug(instance); // logs instances.
matchInstance(instance, conditions)
.then((isMatch) => {
logger.debug("isMatch", isMatch) // logs true/false ?
if (isMatch) {
entities.push(instance);
}
})
.catch((err) => {
logger.error("Failed to match entity: ", err)
})
})
.catch((err) => {
logger.error("Failed to read entity: ", err)
})
});
/*
Promise.resolve(matches)
.then(() => {
setTimeout(() => {
logger.debug("Match count:", entities.length);
logger.debug("Matching entities:", entities) // logs result of entities
}, 5000)
//resolve(entities)
})
*/
Promise.resolve(matches)
.then(() => {
logger.debug("Match count:", entities.length);
logger.debug("Matching entities:", entities) // logs [undefined, undefined ....]
resolve(entities)
})
})
.catch((err) => {
console.error("Failed to parse query: ", err)
});
})
})
}`
The format is a bit broken. I'm quite sure why.
Please let me know if you need more info.
let matches = response.keys.map((key) => {
// the mapping function:
readEntity(key).then((instance) => {
The mapping function does not appear to return a value. This will create a mapped array with undefined elements.
let matches = response.keys.map((key) => {
return readEntity(key).then((instance) => {
// mapping function
may work better by filling matches with promises to read and process entities. Waiting for the promises to be fulfilled can be accomplished with Promise.all, so
Promise.all(matches).then( ... process the entities array
is more likely to work than
Promise.resolve(matches).then( ... process entities
which won't wait for anything asynchronous unless matches is a pending promise - but if that were so, you would not need to resolve it before calling then.
Note the code indentation is confusing, so check the console for errors to see if matches is in scope when processed - I was unable to see that it was.
Related
subject in user.subjects = subjects below is undefined instead of being an array of subjects.
Background:
I have three tables - users, subjects, and a relation between users and subjects called users_subjects.
My goal is that the final res should include an array of subject names.
To do this, I need three asynchronous calls.
Get the users from the users table
Get the subjects is for each user from the table representing the relation between users and subjects (users_subjects)
Get the name for each subject id from subject table
My challenge is getting inputing the subject id and receiving the name. the upper half of addSubjects is my attempt to solve it, albeit unsuccessfully:
//for every user take its id
//run its id with tutors-subjects to get all subject id with that user id
//for each subject id get is subject name through with subjects service
//add subjects key
//add each subject to an array
const addSubject = (knexInstance, users, res) => {
let resultsName = []
function testIt(subjectsArray) {
// let resultsName = []
Promise.all(
subjectsArray.map((id) =>
SubjectsServes.getById(knexInstance, id)
.then(subjectNameObject => {
console.log(subjectNameObject)
resultsName.push(subjectNameObject.subject_name)
})
.catch(err => {
//console.log(err)
})
)).then(() => {
return resultsName
})
}
let results = []
Promise.all(
users.map((user) =>
TutorsSubjectsService.getAllSubjectsForATutor(knexInstance, user.user_id)
.then(subjectsIdArray => {
return testIt(subjectsIdArray)
})
.then(sub => {
user.subjects = sub
results.push(user)
})
.catch(err => {
//console.log(err)
})
)).then(() => {
return res.json(results)
})
.catch(err => {
//console.log(err)
})
}
The following code is where it starts - it calls the above function:
.get((req, res, next) => {
const knexInstance = req.app.get('db');
UsersService.getAllUsers(knexInstance)
.then(Users => {
return addSubject(knexInstance, Users, res)
})
.catch(next)
The only problem I have is with the testIt function. Everything till that point works fine. They're just here for context.
My question is how can I get the correct subjectNames back from testIt?
(The return in testIt last then does not help; the whole function testIt just returns undefined as said in the title)
const testIt = (subjectsArray)=> {
return subjectsArray.map((id) => {
return SubjectsServes.getById(knexInstance, id);
}).reduce((acc,el)=>{
acc.push(el);
return acc;
},[])
//return array of promises
}
const addSubject = (knexInstance, users, res) => {
Promise.all(
users.reduce((acc,el)=>{
acc.push(TutorsSubjectsService.getAllSubjectsForATutor(knexInstance, el.user_id)); return acc;
},[])
).then((userResults) => {
//resolve all promises userResult is an array of result from promises above
Promise.all( userResults.reduce((acc,el)=>{
//for each user create array of promises given the subject list
acc = acc.concat(testIt(el))
} ,[]).then((results)=>{
return res.status(200).send(result);
}).catch( (err)=>{
console.log(err);
return res.status(500).end();
} )
})
.catch(err => {
console.log(err);
return res.status(500).end();
})
}
when you .then() it means you are resolving promises. So nothing return to promise.all. Promise.all().then( result=>{} )
result is an array of output from promises. You have to chain all promises inside each other. They are async, 2 promises all run concurrently. So your result name will be ambiguous, possibly inital value (empty array)
In the following function, after creating the work document, I save the images from the request retrieved through multer module.When saving the image documents I try to update the work document by pushing all the _ids of the images.
But somehow, if you take a look at the code bellow and focus on the console.logs, the second console.log is being executed first, although I used .then when creating the images.That also means that I get an outdated work document on the final lines of code.
The docs say that Model.create() returns a Promise, which means it should run synchronously if I use .then() (if I'm not mistaken). But this is not the case in my function:
function addToDB(req, res, model) {
let imagesToAdd = [];
Works.create(model)
.then(work => {
req.files.forEach(image => {
let path = image.path.split('').map(char => char === '\\' ? '/' : char).join('');
let imageObj = {
fileName: image.filename,
mimeType: image.mimetype,
imageURL: `${req.baseURL}/${path}`,
workId: work._id
};
imagesToAdd.push(imageObj);
});
Images.create(imagesToAdd)
.then(createdImages => {
let imageIds = [];
createdImages.forEach(image => {
console.log(image._id);
imageIds.push(image._id);
});
Works.updateMany({_id: work._id}, {$push: {images: {$each: imageIds}}}).catch(err => handleError(err));
})
.catch(err => handleError(err));
console.log('>'+work._id);
return work._id;
})
.then(workId => {
Works.findById(workId)
.then(foundWork => {
res.json(foundWork);
})
.catch(err => handleError(err));
})
.catch(err => handleError(err));
}
And here is the console after POSTing a work document:
cmd after execution:
And there is the response:
response after execution:
There 2 images were added.Above you saw in the response that images array doesn't have any element, while in mongo the image ids were saved:
The saved work after execution:
The end goal is to respond with the newly created work, which has the image ids included, so I can further populate the images array of the work document and workId in the image document.
How can I make the code run synchronously ?
The problem is that this:
Images.create(imagesToAdd)
.then(createdImages => {
let imageIds = [];
createdImages.forEach(image => {
console.log(image._id);
imageIds.push(image._id);
});
Works.updateMany({_id: work._id}, {$push: {images: {$each: imageIds}}}).catch(err => handleError(err));
})
is set to run asynchronously, then this:
console.log('>'+work._id);
return work._id;
is executed, you go to the next then, and after some time the result of the first promise is returned.
The correct way would be:
function addToDB(req, res, model) {
let imagesToAdd = [];
Works.create(model)
.then(work => {
// Populate imagesToAdd
req.files.forEach(image => {
let path = image.path.split('').map(char => char === '\\' ? '/' : char).join('');
let imageObj = {
fileName: image.filename,
mimeType: image.mimetype,
imageURL: `${req.baseURL}/${path}`,
workId: work._id
};
imagesToAdd.push(imageObj);
});
Images.create(imagesToAdd)
.then(createdImages => {
// images have been created
let imageIds = [];
createdImages.forEach(image => {
console.log(image._id);
imageIds.push(image._id);
});
// imageIds has been populated
Works.updateMany({_id: work._id}, {$push: {images: {$each: imageIds}}})
.then(() => {
// After update is finished, findById
Works.findById( work._id )
.then( foundWork => {
// Return the work after it has been updated with images
res.json( foundWork );
} )
.catch( err => handleError( err ) );
})
.catch(err => handleError(err));
})
.catch(err => handleError(err));
})
.catch(err => handleError(err)); }
Even simpler way is to use async / await.
I have an array (recommendedRoutes) of routes that I need to iterate over and get the owner (User) of the route from MongoDB. I then attach an additional userObj to the route after I get it from the database.
let promiseArr = [];
for (let index = 0; index < obj.recommendedRoutes.length; index++) {
console.log("Pushed: "+index);
promiseArr.push(
User.find({
_id : obj.recommendedRoutes[index].userId,
},(err,data) => {
if(err) {
console.log("Database error: " + err);
}else{
obj.recommendedRoutes[index].userObj = data[0].toObject();
console.log("Completed: "+index);
}
})
);
}
I need to return this array after I have attached the userObj to all the route objects in the array. So I pushed all the queries into a promiseArr and used Promise.all to ensure I only return the array when all the promises have been resolved.
Promise.all(promiseArr)
.then(() => {
console.log("Return Object");
return res.status(200).send({
success: true,
message: "Successfully retrieved Recommended Routes/Carpools",
obj: obj,
});
})
.catch((e) => {
throw "There was an error: "+e;
});
The problem is that it occasionally returns the array before the promises resolves.
Expected output:
Pushed: 0
Pushed: 1
Completed: 1
Completed: 0
Return Object
What happens occasionally:
Pushed: 0
Pushed: 1
Completed: 1
Return Object
Completed: 0
In the name of better code, please await all responses before you start updating your recommendedRoutes.
With you current approach, you update the individual route-objects as soon as the respective response arrives. Between these responses your array is in a transitional state. It doesn't contain the (entirety of) old data anymore, but not yet the (entire) new data from the server; and god forbid that anyone of your requests may fail. How do you roll back OR move forward from this, as each update (every item in the array) is handled individually.
#m1ch4ls answer also has the same problem, it just fixes your use of callbacks instead of the promises.
imho. some better code
Promise.all(
//make all the individual requests
obj.recommendedRoutes.map(
route => User.find({ _id: route.userId })
)
).then(
//and only whe they ALL have returned successfully
data => {
//update all your state at once
data.forEach((item, index) => {
obj.recommendedRoutes[index].userObj = item.toObject()
});
},
err => {
//or return an error-message if anyone fails
console.log("Database error: " + err);
}
);
You are not using Promises but callbacks...
This is the correct way:
for (let index = 0; index < obj.recommendedRoutes.length; index++) {
console.log('Pushed: ' + index);
promiseArr.push(
User.find({
_id: obj.recommendedRoutes[index].userId,
}).then((data) => {
obj.recommendedRoutes[index].userObj = data[0].toObject();
console.log('Completed: ' + index)
}, err => console.log('Database error: ' + err))
);
}
I have an array of strings let symbols = ['abc', 'cde', 'edf', 'qqe', 'hrt'] that I pass as an argument to the function:
async function fetchDetails(symbols) {
let promises = symbols.map((s, i) => {
return fetch(URL + s)
.then(res => return res.json())
.then(json => Object.assign({}, { [s]: json }))
});
console.log('promise:', promises)
return Promise.all(promises);
}
I fetch data from URL/'abc', URL/'cde' etc. and save it into promises array.
But there is an % probability that from the server I will not get all 5 resolved objects. Then in console.log the promises array looks like this:
And I would like to have array containg only 4 resolved items (that I pass to the Promise.all(), instead of 5 (including 4 resolved and 1 with pending status).
If it was a synchronous function, I would simply have to have filtered the array. But I have no access to the [[PromiseStatus]] properties and have no idea how to do this.
Since I am quite new to the Javascript I would appreciate any help with this Async thing. Any scrap or code, or advise where to search for the answer :)
Edit:
Maybe this will help a bit, the route I send a query to, is built like this:
app.get('/data/:symbol', async (req, res) => {
const { params: { symbol } } = req
const data = await stocks.getData(symbol, new Date())
res.send(data)
})
So in the case of error, it doesn't send any error right?
And that's why I could potentially have Pending Status instead of Reject?
SOLUTION OF THE TASK
Hey guys, so I solved this issue with 2 things:
1. Thanks to #Bergi, who pointed to the fact that Pending Status is not something that can be omitted - I checked server side and there was a first problem - Errors were not handled.
2. Then after fixing Server side, since I could separate Resolved Promises from Rejected - I was abble to return Array containing only Resolved promises - using this custom Promise_all solution: https://stackoverflow.com/a/46024590
So my final code looks something like this:
async function fetchDetails(symbols) {
let promises = symbols.map(async (s, i) => {
return await fetch(URL + s)
.then((res)=> {
if (!res.ok) {
throw new Error('Error with fetch')
} else {
return res.json();
}
})
.then(json => Object.assign({}, { [s]: json }))
.catch(err => {
return Promise.reject()})
});
const Promise_all = promises => {
return new Promise((resolve, reject) => {
const results = [];
let count = 0;
promises.forEach((promise, idx) => {
promise
.catch(err => {
return err;
})
.then(valueOrError => {
results[idx] = valueOrError;
count += 1;
if (count === promises.length) resolve(results);
});
});
});
};
const results = await Promise_all(promises)
const validResults = results.filter(result => result !== undefined);
return validResults;
}
Thank you very much to everyone who was writing here!
If you want to fail after a certain timeout you could do this:
const failIn = milliseconds =>
setTimeout(
_=>Promise.reject("timed out")
,milliseconds
);
const Fail = function(details){this.details=details;};
const fetchDetails = symbols =>
Promise.all(
symbols.map((s, i) => {
return Promise.race([
fetch(URL + s),
,failIn(2000)//fail in 2 seconds
])
.then(
response=>[response,s],
err = [new fail([err,URL+s]),s]
)
})
)
.then(
responses =>
responses.map(//map to json object from response or Fail type
([response,s]) =>
(response && response.constructor === Fail)
? response
: Object.assign({}, { [s]: response.json() })
)
);
The Fail objects are still included in the result, you could use filter to take them out if you're just going to ignore them:
.then(
responses =>
responses.filter(
([response,s]) => (response && response.constructor !== Fail)
)
.map(
([response,s]) =>
Object.assign({}, { [s]: response.json() })
)
)
I have an array like this
let array =[ {message:'hello'}, {message:'http://about.com'}, {message:'http://facebook.com'}]
I want to loop through it and at each item, I make a request to server to get open graph data, then save the fetched data back to array. Expected result
array =[
{message:'hello'},
{message: {
url:'http://about.com', title:'about'
}
},
{message:{
url:'http://facebook.com', title:'facebook'
}
}
]
And I need to execute something else after the async fully complete. Below code is what I think it would be
let requests = array.map( (item) => {
return new Promise( (resolve) => {
if (item.message.is('link')) {
axios.get(getOpenGraphOfThisLink + item.message)
.then( result => {
item.message =result.data
// console.log(item.message)
// outputs were
//{url:'http://about.com', title:'about'}
//{url:'http://facebook.com', title:'facebook'}
resolve()
})
}
})
})
Promise.all(requests).then( (array) => {
// console.log (array)
// nothing output here
})
The promise.all() won't run. console.log(array) does not output anything.
I see three main issues with that code:
Critically, you're only sometimes resolving the promise you create in the map callback; if item.message.is('link') is false, you never do anything to resolve. Thus, the Promise.all promise will never resolve.
You're accepting array as an argument to your Promise.all then callback, but it won't be one (or rather, not a useful one).
You're not handling the possibility of a failure from the axios call.
And if we resolve #2 by pre-filtering the array, then there's a fourth issue that you're creating a promise when you already have one to work with.
Instead:
let array = /*...*/;
let requests = array.filter(item => item.message.is('link'))
.map(item => axios.get(getOpenGraphicOfThisLink + item.message)
.then(result => {
item.message = result.data;
return result;
})
);
Promise.all(requests).then(
() => {
// Handle success here, using `array`
},
error => {
// Handle error
}
);
Note how reusing the axios promise automatically propagates the error up the chain (because we don't provide a second callback to then or a catch callback).
Example (doesn't demonstrate errors from axios.get, but...):
// Apparently you add an "is" function to strings, so:
Object.defineProperty(String.prototype, "is", {
value(type) {
return type != "link" ? true : this.startsWith("http://");
}
});
// And something to stand in for axios.get
const axios = {
get(url) {
return new Promise(resolve => {
setTimeout(() => {
resolve({data: "Data for " + url});
}, 10);
});
}
};
// The code:
let array =[ {message:'hello'}, {message:'http://about.com'}, {message:'http://facebook.com'}]
let requests = array.filter(item => item.message.is('link'))
.map(item => axios.get(/*getOpenGraphicOfThisLink + */item.message)
.then(result => {
item.message = result.data;
return result;
})
);
Promise.all(requests).then(
() => {
// Handle success here, using `array`
console.log(array);
},
error => {
// Handle error
console.log(error);
}
);