Array from recursive promises returns undefined - javascript

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);
});
}

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);

Promise not resolving to value when returned

I have a recursive function that resolve a mapped array correctly recursively.
const postCommentsRecursive = (author, permlink) => {
return client.database.call('get_content_replies', [author, permlink])
.then(replies => Promise.all(replies.map(r => {
if (r.children > 0) {
return postCommentsRecursive(r.author, r.permlink)
.then(children => {
r.replies = children;
return r;
})
}else {
return r;
}
})))
}
But when I added another data fetch, the return from then stays a promise:
const postCommentsRecursive = (author, permlink) => {
return client.database.call('get_content_replies', [author, permlink])
.then(replies => Promise.all(replies.map(r => {
//NEW CODE START
const votes = client.database
.call('get_active_votes', [r.author, r.permlink])
.then(av => {
return av;
});
r['active_votes'] = votes;
//NEW CODE END
if (r.children > 0) {
return postCommentsRecursive(r.author, r.permlink)
.then(children => {
r.replies = children;
return r;
})
}else {
return r;
}
})))
}
av is the array value, great. But when I add it to the object from votes, it is a Promise instead.
Each replies child object is still resolved as a value, but inside each r when I add votes to r as r['active_votes'] = votes, the value set in the object is a Promise {<resolved>: Array(1)}.
I can't figure out how to get the actual Array(1) as a value, not the Promise as a value. How do I just get the Array as a value, and not the Promise?
Thank you!
In your code, you're assigning r.votes to be a Promise. Instead, you need to go into the .then of that promise if you want to mutate r. (Conceptually, it's the same as what you're doing in the r.children > 0 block)
return client.database
.call('get_active_votes', [r.author, r.permlink])
.then(av => {
// mutate r
r.active_votes = av;
// now return it so that this entire promise resolves with `r`
return r;
});
async/await generally makes code like this a little clearer:
r.active_votes = await client.database
.call('get_active_votes', [r.author, r.permlink])

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);
}, []);
}
});

Problems with optional promise that has nested promises

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.

Reference error, says variable is not defined

So I have a function which reads from Firebase database and then creates an array of objects. I have defined variable key, but it is still unavailable.
var usersList = [];
const ref = firebase.database().ref()
function fetchUsers() {
ref.child('users').once('value').then(snap => {
var promises = [];
snap.forEach(childSnap => {
var key = childSnap.key
promises.push
(ref.child(`users/${key}/points`).once('value')
);
});
return Promise.all(promises);
}).then(function(snapshots) {
return snapshots.map(snapper => {
var points = snapper.val()
return {uid: key, points: points};
})
}).then(function(usersList) {
console.log(usersList)
})
}
And this is the error I get...
(node:11724) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): ReferenceError: key is not defined
If i just do: key = childSnap.key, then every object's uid is the same.
key is only defined within the forEach callback. Naturally you cannot then reference it from within the second then callback. Moreover, which key would it be? For the first entry? The second? Third?
Instead, you need to return the key with the value:
function fetchUsers() {
ref.child('users').once('value').then(snap => {
var promises = [];
snap.forEach(childSnap => {
var key = childSnap.key;
promises.push(
ref.child(`users/${key}/points`).once('value').then(
snapper => ({key, snapper}) // **
)
);
});
return Promise.all(promises);
}).then(function(snapshots) {
return snapshots.map(({key, snapper}) => { // **
var points = snapper.val();
return {uid: key, points: points};
});
}).then(function(usersList) {
console.log(usersList);
});
}
On the first ** line above, we're transforming the result from once so it returns an object with both the key and "snapper".
On the second ** line, we're using destructured parameters to receive those objects as the distinct key and snapper parameters.
FWIW, if you want to aggressively use concise arrows:
function fetchUsers() {
ref.child('users').once('value').then(snap =>
Promise.all(snap.map(childSnap => {
const key = childSnap.key;
return ref.child(`users/${key}/points`)
.once('value')
.then(snapper => ({key, snapper}));
}))
).then(snapshots =>
snapshots.map(({key, snapper}) => ({uid: key, points: snapper.val()}))
).then(usersList => {
console.log(usersList);
});
}
Also note the use of map rather than declaring an array and pushing to it from a forEach callback. :-)

Categories

Resources