Problems with optional promise that has nested promises - javascript

I have a server GET request where I am trying to create a promise from a function but it's not going too well. I need this promise to be optional, if that's possible, so that if the result is empty it should be ignored (I've marked where it shouldn't carry on with the function with the words IGNORE FUNCTION).
The idea is that it loops through the docs inside the snapshot, and adds them to an array calls jamDocs. Then if that array isn't empty it should loop through each jam and call an async function for each of them. This then sets the jam's hostName. When this is completed, the original promise should return the jamDocs.
If the jamDocs are empty, the whole promise should be ignored. Finally, after other promises are completed (there is one called profilePromise that returns a profile object), the jams should be assigned to the profile object, and the object sent back to the client with a 200 status code.
My code:
var jamsQuery = firebase.db.collection('users')
.where("hostId", "==", id)
.orderBy("createdAt")
.limit(3)
var jamsPromise = jamsQuery.get().then(snapshot => {
var jamDocs = []
snapshot.forEach(doc => {
var jam = doc.data()
jam.id = doc.id
jamDocs.push(jam)
})
if (!snapshot.length) { // Error: Each then() should return a value or throw
return // IGNORE FUNCTION
} else {
var jamNamePromises = jamDocs.map(function(jam) {
var jamRef = firebase.db.collection('users').doc(jam.hostId)
return jamRef.get().then(doc => {
if (doc.exists) {
jam.hostName = doc.data().firstName
return jam
} else {
throw new Error("yeah")
}
})
})
Promise.all(jamNamePromises)
.then(function() {
return jamDocs
})
.catch(...)
}
})
// Later on
Promise.all(profilePromise, jamsPromise)
.then(objectArray => {
var profile = objectArray[0]
myProfile.jams = jamDocs
return res.status(200).send(myProfile);
})
.catch(
res.status(500).send("Could not retrieve profile.")
)
I get errors like: Each then() should return a value or throw and Avoid nesting promises. How do I fix this? Thanks!

Your then on this line doesn't return anything and also has nested promises in it.
var jamsPromise = jamsQuery.get().then(snapshot => {
You should refactor it, to move the nested promises outside of this then:
var jamsPromise = jamsQuery.get()
.then(snapshot => {
var jamDocs = []
snapshot.forEach(doc => {
var jam = doc.data()
jam.id = doc.id
jamDocs.push(jam)
})
if (!snapshot.length) {
return // IGNORE FUNCTION
} else {
var jamNamePromises = getJamDocsPromises();
return Promise.all(jamNamePromises);
})
.catch(...)
});
function getJamDocsPromises(jamDocs) {
return jamDocs.map(function(jam) {
var jamRef = firebase.db.collection('users').doc(jam.hostId)
return jamRef.get().then(doc => {
if (doc.exists) {
jam.hostName = doc.data().firstName
return jam
} else {
throw new Error("yeah")
}
});
}

If the result is empty it should be ignored (shouldn't carry on with the function). If that array isn't empty it should loop through each jam
Don't make it more complicated than it needs to be. Looping over an empty collection does nothing anyway, so you don't need a special case for that. Just continue normally with the empty array.
var jamsPromise = jamsQuery.get().then(snapshot => {
var jamDocs = []
snapshot.forEach(doc => {
jamDocs.push(Object.assign(doc.data(), {id: doc.id}))
})
return Promise.all(jamDocs.map(function(jam) {
return firebase.db.collection('users').doc(jam.hostId).get().then(doc => {
if (doc.exists) {
jam.hostName = doc.data().firstName
return jam
} else {
throw new Error("yeah")
}
})
}))
})
I get errors like: Each then() should return a value or throw
You had forgotten a return in front of the Promise.all.
and Avoid nesting promises. How do I fix this?
There's nothing wrong with nesting promises, but in your case the .then(() => jamDocs) was not necessary since the Promise.all already resolves with an array of all the jams that the inner promises fulfill with.

Related

Promise.All returning empty

I have few api call requests which i am trying to create promises and then execute all using Promise.all but, its giving empty value instead of array. Below is my code.
function getUser(arrUser) {
var student = [];
return new Promise((resolve, reject) => {
if (arrUser.length > 0) {
var promises = arrUseridRequest.map(userRequeset => {
return getRequest(userRequeset).then(result => {
student.push(JSON.parse(result.body).result[0].Name);
console.log(student); //This is giving right details.
}).catch(error => {
reject(error);
});
});
Promise.all(promises).then(StuName => {
resolve(StuName.join());
})
}
});
}
and this is how i am trying to get the values at once:
getUser('123').then(student => {
console.log(Student) //Coming as empty
});
getRequest is my api call nodeJs request module. What's wrong in my code?
All your promises fulfill with the value undefined since you're just logging the student names, but not returning them from the then callback. As you seem to be doing only a single request, the array will be [undefined], which is joined into the empty string.
Also avoid the Promise constructor antipattern:
function getUsers(arrUser) {
const promises = arrUser.map(userId => {
return getRequest(userId).then(result => {
const students = JSON.parse(result.body).result;
return students[0].Name;
});
});
return Promise.all(promises);
}
getUsers(['123']).then(studentNames => {
console.log(studentNames);
console.log(studentNames.join());
}).catch(console.error);

Array from recursive promises returns undefined

I am using Jake Archibald's indexedDB promise wrapper.
I have an object store that contains json objects and a separate auto incremented key. When I retrieve the objects i need to also get the key so i can later delete them.
I'm using iterateCursor to recursively step through the cursor so that i can directly add the key and value to an array which i return as a resolved promise.
static getThings(){
return thingsDb.then(db => {
let keyVals = [];
const tx = db.transaction('things');
const unsyncedStore = tx.objectStore('things');
return unsyncedStore.iterateCursor(cursor => {
if(!cursor) return Promise.resolve(keyVals);
console.log(`key: ${cursor.primaryKey}, val: ${cursor.value}`);
keyVals.push({key: cursor.primaryKey, value: cursor.value});
cursor.continue();
});
}).catch(error => {
console.error(error);
});
}
however when I call
DBHelper.getThings().then(returnedArray=> {
// do something with returnedArray
})
it throws an error saying the returned array is undefined.
iterateCursor doesn't return anything (i.e. returns undefined)
You need to return the promise available at unsyncedStore.complete
But this promise won't resolve to a useful value, so, use .then to return the keyVals
Also, no point in doing if(!cursor) return Promise.resolve(keyVals); since the return value from the iterateCursor callback is ignored
static getThings() {
return thingsDb.then(db => {
let keyVals = [];
const tx = db.transaction('things');
const unsyncedStore = tx.objectStore('things');
// no return here since iterateCursor doesn't return anything useful anyway
unsyncedStore.iterateCursor(cursor => {
if (!cursor) return;
console.log(`key: ${cursor.primaryKey}, val: ${cursor.value}`);
keyVals.push({key: cursor.primaryKey, value: cursor.value});
cursor.continue();
});
// return here - complete is a promise that resolves when the iterateCursor completes
return unsyncedStore.complete.then(() => keyVals);
}).catch(error => {
console.error(error);
});
}

Returning value from multiple promises within Meteor.method

After a bunch of looking into Futures, Promises, wrapAsync, I still have no idea how to fix this issue
I have this method, which takes an array of images, sends it to Google Cloud Vision for logo detection, and then pushes all detected images with logos into an array, where I try to return in my method.
Meteor.methods({
getLogos(images){
var logosArray = [];
images.forEach((image, index) => {
client
.logoDetection(image)
.then(results => {
const logos = results[0].logoAnnotations;
if(logos != ''){
logos.forEach(logo => logosArray.push(logo.description));
}
})
});
return logosArray;
},
});
However, when the method is called from the client:
Meteor.call('getLogos', images, function(error, response) {
console.log(response);
});
the empty array is always returned, and understandably so as the method returned logosArray before Google finished processing all of them and returning the results.
How to handle such a case?
With Meteor methods you can actually simply "return a Promise", and the Server method will internally wait for the Promise to resolve before sending the result to the Client:
Meteor.methods({
getLogos(images) {
return client
.logoDetection(images[0]) // Example with only 1 external async call
.then(results => {
const logos = results[0].logoAnnotations;
if (logos != '') {
return logos.map(logo => logo.description);
}
}); // `then` returns a Promise that resolves with the return value
// of its success callback
}
});
You can also convert the Promise syntax to async / await. By doing so, you no longer explicitly return a Promise (but under the hood it is still the case), but "wait" for the Promise to resolve within your method code.
Meteor.methods({
async getLogos(images) {
const results = await client.logoDetection(images[0]);
const logos = results[0].logoAnnotations;
if (logos != '') {
return logos.map(logo => logo.description);
}
}
});
Once you understand this concept, then you can step into handling several async operations and return the result once all of them have completed. For that, you can simply use Promise.all:
Meteor.methods({
getLogos(images) {
var promises = [];
images.forEach(image => {
// Accumulate the Promises in an array.
promises.push(client.logoDetection(image).then(results => {
const logos = results[0].logoAnnotations;
return (logos != '') ? logos.map(logo => logo.description) : [];
}));
});
return Promise.all(promises)
// If you want to merge all the resulting arrays...
.then(resultPerImage => resultPerImage.reduce((accumulator, imageLogosDescriptions) => {
return accumulator.concat(imageLogosDescriptions);
}, [])); // Initial accumulator value.
}
});
or with async/await:
Meteor.methods({
async getLogos(images) {
var promises = [];
images.forEach(image => {
// DO NOT await here for each individual Promise, or you will chain
// your execution instead of executing them in parallel
promises.push(client.logoDetection(image).then(results => {
const logos = results[0].logoAnnotations;
return (logos != '') ? logos.map(logo => logo.description) : [];
}));
});
// Now we can await for the Promise.all.
const resultPerImage = await Promise.all(promises);
return resultPerImage.reduce((accumulator, imageLogosDescriptions) => {
return accumulator.concat(imageLogosDescriptions);
}, []);
}
});

Firestore + Nested Promise and ForEach + React-Native

Alright so I kinda got stuck doing nested queries with firestore because of ForEach and nested Promise returns. From all the previous posts on stackoverflow I understood quite a bit about promises and then nesting them inside ForEach. However, I couldnt find a solution where I nest promise inside forEach which then has another forEach inside it.
I do understand that I have to use Promise.all to make the root forEach wait but how to tell it to wait until the .then() also returns something?
firebase.firestore().collection("Feeds").get().then(feedsSnapshot => {
let promiseArray = [], anotherPromiseArray = [];
feedsSnapshot.forEach(function (feed) {
let promise = checkifILikedTheFeed(feed).then(like => {
return like;
}).then(like => {
let anotherPromise = firebase.firestore().collection("Feeds").doc(feed.id).collection("Likes").get()
.then(likesSnapshot => {
if(likesSnapshot.empty){
return {
// return myData
};
} else {
let countLikes = 0;
likesSnapshot.forEach(likeDoc => {
if(likeDoc.data().isLike){
countLikes++;
}
});
return {
//myData + countLikes
};
}
});
anotherPromiseArray.push(anotherPromise);
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// I thought this would work, sadly doesnt.
});
promiseArray.push(promise);
});
return Promise.all([promiseArray,anotherPromiseArray]);
}).then(feeds => {
console.log(feeds);
// ^^^^^^^^^^^^^^^^^^^
// returns [[undefined],[undefined]]
dispatch({
type: 'RETRIEVE_FEEDS',
payload: feeds,
});
});

Returning one object that's built with nested promises

I'm struggling to wrap my head around a nested promise layout where one one object is returned at the end of it. My current code is as follows:
router
router.get(`/${config.version}/event/:id?`, function (req, res, next) {
var event = new Event(req, res, next);
event.getInfo(req.params.id).then((info) => {
res.send(info);
});
});
function
getInfo(id) {
db.main('events').where('id', id).select()
.then((result) => {
if(result.length > 0) {
var event = result[0];
//regular functions
event.status = this.getStatus(id);
event.content = this.getContent(id);
event.price = this.getPrice(id);
//promise functions
var users = this.getUsers(id);
var hosts = this.getHosts(id);
Promise.all([users, hosts]).then(values => {
event.users = values[0];
event.hosts = values[1];
//return whole event object to router
return event;
})
.catch((err) => {
return {
result: 'error',
error: err
};
});
} else {
return {
result: 'error',
error: "Event does not exist"
};
}
}).catch((e) => {
return {
result: 'error',
error: "Could not retrieve event info"
};
});
}
As you can see, the router initiates a call to get info about an event. The function then does a database call and gets some event data. Thereafter I need to get the users and hosts of the event from a different table, append that info to the event object as well and then return the whole object to the router to be sent to the client.
When I do this I get an error because I'm not returning a promise from the getInfo function, but I'm not sure how or which promise I'm supposed to return.
I'd appreciate some help with this. Thanks
using .then means that you are returning a promise.
function getInfo(id) {
return new Promise(function(resolve, reject) {
resolve('yay!');
})
}
getInfo().then(function(result) { //result = yay! });
to make your code work, simply replace all the returns with resolves, the errors with rejects, and wrap the whole thing with a return new Promise as i did.
getInfo(id) {
return new Promise(function(resolve, reject) {
db.main('events').where('id', id).select()
.then((result) => {
if (result.length > 0) {
var event = result[0];
//regular functions
event.status = this.getStatus(id);
event.content = this.getContent(id);
event.price = this.getPrice(id);
//promise functions
var users = this.getUsers(id);
var hosts = this.getHosts(id);
Promise.all([users, hosts]).then(values => {
event.users = values[0];
event.hosts = values[1];
//return whole event object to router
resolve(event);
})
.catch((err) => {
reject({
result: 'error',
error: err
});
});
} else {
reject({
result: 'error',
error: "Event does not exist"
});
}
}).catch((e) => {
reject({
result: 'error',
error: "Could not retrieve event info"
});
});
});
}
Just wrap your async code in Promise like this:
getInfo(id) {
return new Promise(function(resolve, reject) {
db.main('events').where('id', id).select()
.then((result) => {
//...
resolve(/* result */)
// OR
reject(/* Error */)
})
}
Note: Use resolve and reject instead return
It's a combination of several things, but the main one is that you are never returning anything from getInfo, so your router handler is calling .then on undefined.
Do not call .catch (without throwing inside it) on Promises you intend to return for a caller to consume. This makes it not possible to use .catch, because you recovered the Promise chain into a resolved one.
Whatever you return inside a .then will be merged into the promise chain, so it's not actually a "Promise that resolves with a Promise". Your whole code could be replaced with:
getInfo (id) {
return db.main('events').where('id', id).select()
.then(result => {
if (result.length == 0) {
// you can also just throw your error object thing,
// but standard Error are generally the convention
throw new Error('Event does not exist')
}
const [event] = result
event.status = this.getStatus(id)
event.content = this.getContent(id)
event.price = this.getPrice(id)
return Promise.all([this.getUsers(id), this.getHosts(id)])
.then(([users, hosts]) => {
event.users = users
event.hosts = hosts
// this is the only value that
// this.getInfo(id).then(value => {/* ... */}) will see
return event
}
})
}

Categories

Resources