I was having some problem with multi-level of promises. What I tried to do is first get list of receipt items under certain category, then for each receipt item, I get its detail & receipt ID, after I get the receipt ID, I search for the account ID. Then, I get the account details based on account ID. Here is my code:
var query = // get receipt items under certain category
var outerPromise = query.once('value').then(data => {
var promises = [];
var datasetarr = [];
data.forEach(snapshot => {
var itemData = // get receipt item unique push ID
var query = // get details of receipt items
var promise = query.once('value').then(data => {
var itemDetail = // get receipt item detail
if(type == subtype){
var receiptID = itemDetail.receiptID;
var query = // query receipts table by receiptID
return query.once('value').then(data => {
data.forEach(snapshot => {
snapshot.forEach(childSnapshot => {
if(childSnapshot.key == receiptID){
var accountKey = // get accountID
var query = // query accounts table
return query.once('value').then(data => {
var accountDetail = data.val();
var age = accountDetail.age;
var gender = accountDetail.gender;
console.log(age + ' ' + gender);
datasetarr.push({age: age, gender: gender});
});
}
});
});
});
}
});
promises.push(promise);
});
return Promise.all(promises).then(()=> datasetarr);
});
I managed to print out the result from the console.log above. However, when I tried to print out here which is when the promise is done:
outerPromise.then((arr) => {
console.log('promise done');
for(var i = 0; i < arr.length; i++){
console.log(arr[i].age + ' ' + arr[i].gender);
}
});
I get nothing here. The console now is showing 'promise done' first before any other results I printed out above.
How can I do this correctly?
I will provide a more detailed explanation in a couple of hours, I have a prior engagement which means I can't provide details now
First step to a "easy" solution is to make a function to make an array out of a firebase snapshot, so we can use map/concat/filter etc
const snapshotToArray = snapshot => {
const ret = [];
snapshot.forEach(childSnapshot => {
ret.push(childSnapshot);
});
return ret;
};
Now, the code can be written as follows
// get list of receipt items under category
var query // = // get receipt items under certain category
var outerPromise = query.once('value').then(data => {
return Promise.all(snapshotToArray(data).map(snapshot => {
var itemData // = // get receipt item unique push ID
var query // = // get details of receipt items
return query.once('value').then(data => {
var itemDetail // = // get receipt item detail
if(type == subtype){
var receiptID = itemDetail.receiptID;
var query //= // query receipts table by receiptID
return query.once('value').then(data => {
return Promise.all([].concat(...snapshotToArray(data).map(snapshot => {
return snapshotToArray(snapshot).map(childSnapshot => {
if(childSnapshot.key == receiptID){
var accountKey //= // get accountID
var query //= // query accounts table
return query.once('value').then(data => {
var accountDetail = data.val();
var age = accountDetail.age;
var gender = accountDetail.gender;
console.log(age + ' ' + gender);
return({age, gender});
});
}
}).filter(result => !!result);
}).filter(result => !!result)));
});
}
});
})).then([].concat(...results => results.filter(result => !!result)));
});
To explain questions in the comments
[].concat used to add the content of multiple arrays to a new array, i.e
[].concat([1,2,3],[4,5,6]) => [1,2,3,4,5,6]
...snapshotToArray(data).map(etc
... is the spread operator, used as an argument to a function, it takes the iterable and "spreads" it to multiple arguments
console.log(...[1,2,3]) == console.log(1,2,3)
In this case snapshotToArray(data).map returns an array of arrays, to give a console log example
console.log(...[[1,2],[3,4]]) == console.log([1,2], [3,4])
adding the concat
[].concat(...[[1,2],[3,4]]) == [].concat([1,2],[3,4]) == [1,2,3,4]
so it flattens a two level array to a single level, i.e.
console.log(...[[1,2],[3,4]]) == console.log(1,2,3,4)
So in summary, what that code fragment does is flatten a two level array
filter(result => !!result)
simply "filters" out any array elements that are "falsey". As you have this condition
if(childSnapshot.key == receiptID){
if that is false, the result will be undefined for that map - all other results will be an array, and even empty arrays are truthy - that's why the filtering is done so often! There's probably a better way to do all that, but unless you're dealing with literally millions of items, there's no real issue with filtering empty results like this
End result is a flat array with only the Promises returned from the code within
Related
My Firebase data base contains JSON objects, each with the same parameters as seen below. Firebase data
I want to get an array that has each objects country. So if I have 4,000 objects I want to get an array of 4,000 strings all containing a country.
Right now I can get the console to log all the 4,000 objects into an array using the code below.
componentWillMount() {
this.fetchData();
}
fetchData = async () => {
var data1 = [];
var fireBaseResponse = firebase.database().ref();
fireBaseResponse.once('value').then(snapshot => {
snapshot.forEach(item => {
var temp = item.val();
data1.push(temp);
return false;
});
console.log(data1);
});
}
But when I try doing
var fireBaseResponse = firebase.database().ref().child('country');
I get an array of nothing.
Any help would be great.
As mentioned in the comments, you can create a new temp object containing just country before pushing it into your array.
snapshot.forEach(item => {
var temp = { country: item.val().country };
data1.push(temp);
return false;
});
Whenever a user does a product search in our app we need to cache the search results in local storage so that when they are offline and they perform a search it can pull up previously searched items. With each new search we need to only add the new items to the searchCache. So if they search for 'be' first then search for 'b' there will be overlapping results but we don't want to add items that are already stored. This is currently not working though because it still returns all of the items in the search instead of filtering them. Does anyone know how to achieve this?
I want to filter the searchResults by their ids. Comparing ids to return the items that have ids that are not already in the searchCache.
newItems = searchResults.filter((searchResult) => {
return searchCache.find(searchCacheItem => searchCacheItem.id !== searchResult.id);
});
Here is the entire action function:
export function handleProductSearch(searchTerm) {
return (dispatch, getState) => {
return dispatch(getProductList(searchTerm)).then((results) => {
const searchResults = results.items;
// Get previously stored product search cache
AsyncStorage.getItem(STORAGE_KEY_PRODUCT_SEARCH_CACHE).then((results) => {
return JSON.parse(results);
}).then((response) => {
const searchCache = response || [];
let newItems = [];
if (searchCache.length > 0) {
// Check for duplicates in the recently added items. Only add the items that are new and not already in the searchCache.
newItems = searchResults.filter((searchResult) => {
return searchCache.find(searchCacheItem => searchCacheItem.id !== searchResult.id);
});
}
searchCache.push(newItems.length > 0 ? newItems : searchResults);
AsyncStorage.setItem(STORAGE_KEY_PRODUCT_SEARCH_CACHE, JSON.stringify(searchCache));
}).catch(err => {
console.log('Error retrieving product search cache: ', err);
});
});
};
}
You should first check if the item is already exists and then if not push it to the array.
Sample
let newItems = [];
if (searchCache.length > 0) {
// Check for duplicates in the recently added items. Only add the items that are new and not already in the searchCache.
searchResults.forEach(searchResultItem => {
if(!searchCache.find(searchCacheItem => searchCacheItem.id === searchResultItem.id)) newItems.push(item)
});
}
This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 5 years ago.
So I am trying to get data with AsyncStorage.getItem and then pass it to a function in React-native. but when I do that I get this error "data.filter is not a function" from my function. I think that the problem could be that I am not getting the data but insted a promise.
Constructor:
constructor(props) {
super(props)
const getSectionData = (dataBlob, sectionId) => dataBlob[sectionId];
const getRowData = (dataBlob, sectionId, rowId) => dataBlob[`${rowId}`];
const ds = new ListView.DataSource({
rowHasChanged: (r1, r2) => r1 !== r2,
sectionHeaderHasChanged : (s1, s2) => s1 !== s2,
getSectionData,
getRowData,
});
let data = AsyncStorage.getItem('connections').then((token) => {
token = JSON.parse(token);
return token;
});
const {dataBlob, sectionIds, rowIds} = this.formatData(data);
// Init state
this.state = {
dataSource: ds.cloneWithRowsAndSections(dataBlob, sectionIds, rowIds),
left: true,
center: false,
right: false
}
}
Function:
formatData(data) {
// We're sorting by alphabetically so we need the alphabet
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
// Need somewhere to store our data
const dataBlob = {};
const sectionIds = [];
const rowIds = [];
// Each section is going to represent a letter in the alphabet so we loop over the alphabet
for (let sectionId = 0; sectionId < alphabet.length; sectionId++) {
// Get the character we're currently looking for
const currentChar = alphabet[sectionId];
// Get users whose first name starts with the current letter
const users = data.filter((user) => user.nickname.toUpperCase().indexOf(currentChar) === 0);
// If there are any users who have a first name starting with the current letter then we'll
// add a new section otherwise we just skip over it
if (users.length > 0) {
// Add a section id to our array so the listview knows that we've got a new section
sectionIds.push(sectionId);
// Store any data we would want to display in the section header. In our case we want to show
// the current character
dataBlob[sectionId] = { character: currentChar };
// Setup a new array that we can store the row ids for this section
rowIds.push([]);
// Loop over the valid users for this section
for (let i = 0; i < users.length; i++) {
// Create a unique row id for the data blob that the listview can use for reference
const rowId = `${sectionId}:${i}`;
// Push the row id to the row ids array. This is what listview will reference to pull
// data from our data blob
rowIds[rowIds.length - 1].push(rowId);
// Store the data we care about for this row
dataBlob[rowId] = users[i];
}
}
}
return { dataBlob, sectionIds, rowIds };
}
So my question is once I have the promise what should I do with it in order to pass it to my function and make this line work const users = data.filter((user) => user.nickname.toUpperCase().indexOf(currentChar) === 0);?
Yea you're not waiting for the promise to resolve, you can either turn the outer function in an async function and await for data to be resolved or whatever you put in the .then gets ran after the promise resolves. Also moving into componentDidMount. You might also want to look into FlatList instead of ListView:
componentDidMount(){
AsyncStorage.getItem('connections').then((token) => {
const token = JSON.parse(token);
const {dataBlob, sectionIds, rowIds} = this.formatData(token);
// Update State
this.setState({
dataSource: ds.cloneWithRowsAndSections(dataBlob, sectionIds, rowIds)
});
});
}
I need to create a new array from iterating mongodb result. This is my code.
const result = await this.collection.find({
referenceIds: {
$in: [referenceId]
}
});
var profiles = [];
result.forEach(row => {
var profile = new HorseProfileModel(row);
profiles.push(profile);
console.log(profiles); //1st log
});
console.log(profiles); //2nd log
I can see update of profiles array in 1st log. But 2nd log print only empty array.
Why i couldn't push item to array?
Update
I think this is not related to promises. HorseProfileModel class is simply format the code.
const uuid = require("uuid");
class HorseProfileModel {
constructor(json, referenceId) {
this.id = json.id || uuid.v4();
this.referenceIds = json.referenceIds || [referenceId];
this.name = json.name;
this.nickName = json.nickName;
this.gender = json.gender;
this.yearOfBirth = json.yearOfBirth;
this.relations = json.relations;
this.location = json.location;
this.profilePicture = json.profilePicture;
this.horseCategory = json.horseCategory;
this.followers = json.followers || [];
}
}
module.exports = HorseProfileModel;
await this.collection.find(...)
that returns an array of the found data right? Nope, that would be to easy. find immeadiately returns a Cursor. Calling forEach onto that does not call the sync Array.forEach but rather Cursor.forEach which is async and weve got a race problem. The solution would be promisifying the cursor to its result:
const result = await this.collection.find(...).toArray();
Reference
I have been working around this for a really long while and I think I'm sort of giving up and coming here to ask now. I have a cloud code function that first lets me get a list of categories. Every category has multiple products. This one-to-many relationship is carried out using pointers, where every product points to the category it belongs to. However I'm failing to retrieve any products.
This my code,
Parse.Cloud.define("GetCategories", function(request, response) {
var _ = require('underscore.js')
var MSTCATEGORY_CLASS = "MSTCategory";
var MSTPRODUCT_CLASS = "MSTProduct";
var CATEGORY_TAG = "Category";
var SHOP_TAG = "Shop";
var VIEWS_TAG = "Views";
var CAT_LIMIT = 10;
var PROD_LIMIT = 5;
var productList = [];
var CatAndProd = [];
var MSTCategory = Parse.Object.extend(MSTCATEGORY_CLASS);
var query = new Parse.Query(MSTCategory);
query.limit(CAT_LIMIT);
query.find().then(function(results) {
var promise = Parse.Promise.as();
_.each(results, function(result) {
promise = promise.then(function() {
var MSTProduct = Parse.Object.extend(MSTPRODUCT_CLASS);
var ProdQuery = new Parse.Query(MSTProduct);
ProdQuery.equalTo(CATEGORY_TAG, result);
ProdQuery.include(CATEGORY_TAG);
ProdQuery.include(SHOP_TAG);
ProdQuery.descending(VIEWS_TAG);
ProdQuery.limit(PROD_LIMIT);
var category = result;
ProdQuery.find().then(function(ProdResults){
ProdResults.forEach(function(product) {
productList.push(product);
});
var singleItem = {
"category" : category,
"products" : productList
};
CatAndProd.push(singleItem);
return Parse.Promise.as("Hello!");
});
});
});
return promise;
}).then(function(hello) {
var jsonObject = {
"categoryAndProducts": CatAndProd
};
response.success(jsonObject);
});
});
What I am trying to do is after getting the category, I'd fetch products in it, add them to a jsonObject. And once I'm done with all categories. I'll create an array to carry all those json objects and send it as response. This is really basic, I'm pretty sure it's become my logic is incorrect. I'm new to Javascript and Parse.
There are few errors:
in your _.each, you are overwriting the same promise object in each loop
you can return directly your singleItem via the promise resolving, not need for an extra array
you're using a global productList array so the last category will have ALL products.
Try like this, I put few comments in the code:
query.find().then(function(results) {
// an array to store all our promises
var promises = [];
results.forEach(function(result) {
// one promise for each result
var promise = new Parse.Promise();
var MSTProduct = Parse.Object.extend(MSTPRODUCT_CLASS);
var ProdQuery = new Parse.Query(MSTProduct);
ProdQuery.equalTo(CATEGORY_TAG, result);
ProdQuery.include(CATEGORY_TAG);
ProdQuery.include(SHOP_TAG);
ProdQuery.descending(VIEWS_TAG);
ProdQuery.limit(PROD_LIMIT);
var category = result;
ProdQuery.find().then(function(ProdResults) {
// remove this
// ProdResults.forEach(function(product) {
// productList.push(product);
// });
var singleItem = {
"category" : category,
"products" : ProdResults
};
// pass singleItem directly when resolving the promises
promise.resolve(singleItem);
});
promises.push(promise);
});
// return a promise resolving when ALL Promises are resolved
return Parse.Promise.when(promises);
}).then(function(allProducts) {
// allProducts is an array of singleItem
var jsonObject = {
"categoryAndProducts": allProducts
};
response.success(jsonObject);
});
Also, try to not mix Array.forEach and _.each, use one of them but not both at the same time.