Get fullData when CollectionGroup is found - javascript

I have a problem querying all data from 1 collection
User-> UidUser-> InfoUser (POST (Collection), name, age ...)
I then used the code below to get the collectionsGroup (Post) but how do I get the User's Name and age?
When I use 2 nested loops, another problem is that forEach cannot use Await and Async inside it makes me lose data.
getData = async () => {
await firebase.firestore().collection('user').doc(Fire.shared.uid).onSnapshot(documentSnapshot => {
firebase.firestore().collectionGroup('Post').where('uid', 'in',
documentSnapshot.data().Followings).get().then(querySnapshot => {
const Post = [];
querySnapshot.forEach(post => {
Post.push({ data: post.data() }) // This is correct and get full data's Post
});
this.setState({ dataPost: Post })
})
})
}
await firebase.firestore().collection('user').doc(Fire.shared.uid).onSnapshot(documentSnapshot => {
firebase.firestore().collectionGroup('Post').where('uid', 'in', documentSnapshot.data().Followings).get().then(querySnapshot => {
const Post = [];
querySnapshot.forEach(async (post) => {
// Incorrect because forEach don't wait callback => Lose a documents's user //
await firebase.firestore().collection('user').doc(post.data().id).onSnapshot(user => {
Post.push({ data: post.data(),user: documentSnapshot.data() })
})
});
this.setState({ dataPost: Post })
})
})

I think promise.all resolve this problem
firebase.firestore()
.collection("user")
.doc(Fire.shared.uid)
.onSnapshot((documentSnapshot) => {
firebase
.firestore()
.collectionGroup("Post")
.where("uid", "in", documentSnapshot.data().Followings)
.get()
.then((querySnapshot) => {
const Post = [];
querySnapshot.forEach((post) => {
Post.push(new Promise((resolve, reject) => {
firebase
.firestore()
.collection("user")
.doc(post.data().id)
.onSnapshot((user) => {
resolve({ data: post.data(), user: documentSnapshot.data() });
});
}))
});
Promise.all(Post).then(res => {
console.log(res)
this.setState({ dataPost: res})
})
});
});
I show how it's work with a simple example with setTimeout function & forEach function maybe it helps others when facing this kind of problems
async function call() {
const nums = [1, 2, 3, 4, 5];
const promises = [];
nums.forEach((res) => {
setTimeout(() => {
promises.push(res*2);
}, 10000);
});
console.log(promises);
}
call();
async function call() {
const nums = [1, 2, 3, 4, 5];
const promises = [];
nums.forEach((res) => {
setTimeout(() => {
promises.push(res*2);
}, 10000);
});
Promise.all(promises).then((res) => {
console.log("res", res);
});
}
call();
in the above examples, output was an empty array so I figure out a way to fix this issue
async function call() {
const nums = [1, 2, 3, 4, 5];
const promises = [];
nums.forEach((res) => {
promises.push(
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(res * 2);
}, 3000);
})
);
});
Promise.all(promises).then((res) => {
console.log("res", res);
});
}
call();

Related

Not receiving the correct data from Promises?

I have a function that fetches data from an API, and the function works correctly as intended:
const getStockData = async (stock) => {
try {
const response = await axios.get(`${BASE_URL}${stock}${KEY_URL}`);
console.log(response);
return response;
} catch (error) {
console.error('Error', error.message);
}
};
And I have another function that gets data from my firebase which then passes in the .ticker into the function above however when I log the response from the promise the data is returned null
Is there a reason why its not working as intended?
const getMyStocks = async () => {
let promises = [];
let tempData = [];
const querySnapshot = await getDocs(collection(db, 'myStocks'));
querySnapshot.forEach((doc) => {
console.log(doc.data().ticker);
promises.push(
getStockData(doc.data().ticker).then((res) => {
console.log(res);
tempData = {
id: doc.id,
data: doc.data(),
info: res.data,
};
})
);
getMyStocks must return the resolution of the promises it creates...
// to reduce nested promises, this takes an FB doc and adds the getStockData query to it
const getFBAndTickerData = async doc => {
return getStockData(doc.data().ticker).then(res => {
console.log(res);
return {
id: doc.id,
data: doc.data(),
info: res.data,
};
});
}
const getMyStocks = async () => {
const querySnapshot = await getDocs(collection(db, 'myStocks'));
let promises = querySnapshot.docs.map(doc => {
console.log(doc.data().ticker);
return getFBAndTickerData(doc);
});
return Promise.all(promises);
}

Returning data out from promise then chain

I have a class method (for 'apollo-datasource-rest' that is supposed to fetch a plan from a plan_id. I need to hit two endpoints and combine the data. This works with Promise.all([promise1, promise2]) and then passes the user_ids on to the next Promise.all method that calls the GET /users endpoint multiple times. If I console.log out the usersArray that is returned, I get the array of users, but if I try to return that array, it doesn't get assigned to the objToReturn variable. I also need to add data from snapshot2 to the objToReturn but that is secondary.
getPlanById = async ( planId ) => {
const promise1 = new Promise((resolve) => {
return resolve(this.get('/url1'))
});
const promise2 = new Promise((resolve) => {
return resolve(this.get('/url2'))
});
const objToReturn = await Promise.all([promise1, promise2])
.then(([snapshot1, snapshot2]) => {
return snapshot1.user_ids
})
.then((userIds) => {
this.getUsersFromUserIds(userIds).then((usersArray) => {
console.log(usersArray)
// return usersArray doesn't assign to objToReturn
})
})
return objToReturn
}
getUsersFromUserIds(userIds) {
let userPromises = []
userIds.forEach((uid) => {
const promise = this.get(`/users/${uid}`)
.then((response) => {
if (response.status === 'success') {
return response.data.user
} else {
return null
}
})
userPromises.push(promise)
})
return Promise.all(userPromises).
then((userPromiseData) => {
return userPromiseData
})
}
You need to return the promise of this.getUsersFromUserIds(userIds). So that the promise chain can work.
E.g.
index.js:
class UserAPI {
async getPlanById(planId) {
const promise1 = Promise.resolve({ user_ids: [1, 2] });
const promise2 = Promise.resolve();
const objToReturn = await Promise.all([promise1, promise2])
.then(([snapshot1, snapshot2]) => {
return snapshot1.user_ids;
})
.then((userIds) => {
// You need to return here
return this.getUsersFromUserIds(userIds).then((usersArray) => {
return usersArray;
});
});
return objToReturn;
}
async getUsersFromUserIds(userIds) {
return [
{ id: 1, name: 'a' },
{ id: 2, name: 'b' },
];
}
}
const userAPI = new UserAPI();
userAPI.getPlanById(1).then((objToReturn) => {
console.log('objToReturn: ', objToReturn);
});
The output in the console:
objToReturn: [ { id: 1, name: 'a' }, { id: 2, name: 'b' } ]

Asynchronous code in nested forEach loop- react native

I have input data that is formatted as such:
[ [4, 1, 2], [2, 5] ]
I want to make an api call for each of the numbers in the array, and have output as such:
[ [response_4, response_1, response_2], [response_2, response_5] ]
I've been stuck on this logic for two days-- I can't get my return array formatted correctly. It instead returns:
[ response_4, response_1, response _2, response_2, response_5 ]
I know I'm doing something wrong in terms of using promises/async, and also I know I need to reset temp to length = 0 at some point, but every time I add that in, it will simply return [] as my output. Any advice/help?
const getNumData = (data) => {
let temp = []
return new Promise((resolve, reject) => {
data.forEach((outerArray) => {
return new Promise((resolve, reject) => {
outerArray.forEach((number) => {
return fetch(`http://127.0.0.1:8000/api/number?id=${number}`, {method: 'GET',})
.then((response) => response.json())
.then((responseJson) => {
temp = this.state.seqDone.concat(responseJson[0]);
this.setState({
seqDone: temp
})
console.log(temp)
})
})
if (this.state.seqDone) {
console.log(this.state.seqDone)
resolve(this.state.seqDone);
} else {
reject(Error('Sequences not found'));
}
})
});
if (this.state.seqDone) {
console.log(this.state.seqDone)
resolve(this.state.seqDone);
} else {
reject(Error('Sequences not found'));
}
})
}
You can do it in this way
const nestedPromise = async (items = []) => {
return await Promise.all(
items.map(async item => {
if (Array.isArray(item) && item.length) {
return await nestedPromise(item)
}
// return await call to your function
return 'response-' + item
})
)
}
const items = [ [4, 1, 2], [2, 5] ]
nestedPromise(items).then(results => {
console.log(results)
})
Promise.all accepts array of functions as arguments, thoses functions will be executed asynchronously. In your case you just have to use it recursively
fetchData = (item) => {
return fetch(`http://127.0.0.1:8000/api/pose?id=${item}`)
.then (response => response.json())
}
constructArray = (items) => {
Promise.all(items.map(nestedArray => {
return Promise.all(nestedArray.map(this.fetchData))
}
))
.then((results) => {
console.log(JSON.stringify(results))
})
}

sinon spy doesn't register call in a generator loop?

I want to check that a piece of code is being called, so I'm using a sinon spy to assert this. However, the spy seems to be failing, despite console.logs showing that the code has been called correctly.
I'm wondering if my function being a generator is causing my spy to misreport what it's doing.
my code (i've taken out some chunks for brevity):
isBlacklisted(release, jobUUID) {
names.forEach((name) => {
this._spawnPythonProcessGenerator(
this.IS_BLACKLISTED_SCRIPT,
name
).next().value
.then((data) => {
console.log(data);
})
.catch((err) => {
this._errorEvent(release, name, err, jobUUID);
});
}, this);
}
_errorEvent(release, name, err, jobUUID) {
console.log('got here');
}
*_spawnPythonProcessGenerator(scriptSrc, name) {
const pythonProcess = this._childProcess.spawn(
'python3',
[...arguments]
);
yield new Promise((resolve, reject) => {
pythonProcess.stderr.on('data', (err) => {
reject(err.toString());
});
pythonProcess.stdout.on('data', (data) => {
resolve(data.toString());
});
});
}
and my tests:
const Blacklist = require('../../src/Blacklist2');
const childProcess = require('child_process');
const uuid = require('uuid/v4');
describe('Blacklist', () => {
let blacklist;
beforeEach(() => {
blacklist = new Blacklist(childProcess);
blacklist.IS_BLACKLISTED_SCRIPT = './test/helpers/good.py';
});
describe('isBlacklisted', () => {
it('should call the _errorEvent for every name in a release when the blacklist application is not available', async () => {
let release = {
id: 1001,
asset_controller: {
id: 54321,
},
display_name: 'Blah',
names: [
{
id: 2001,
name: 'Blah',
},
],
};
blacklist.IS_BLACKLISTED_SCRIPT = './test/helpers/'+ uuid() +'.py';
const spy = sinon.spy(blacklist, '_errorEvent');
blacklist.isBlacklisted(release, uuid());
console.log(spy);
sinon.assert.calledTwice(spy);
spy.restore();
});
});
});
my spy reports:
notCalled: true
I'll expand my comment into an actual answer, hopefully that helps.
Your problem lies with asynchrony, not with the generator. You need isBlacklisted to return a promise you can wait on. Otherwise your assertion happens before the spy is called.
Something like this:
isBlacklisted(release, jobUUID) {
let promises = names.map((name) => {
return this._spawnPythonProcessGenerator(
this.IS_BLACKLISTED_SCRIPT,
name
).next().value
.then((data) => {
console.log(data);
})
.catch((err) => {
this._errorEvent(release, name, err, jobUUID);
});
}, this);
return Promise.all(promises);
}
Then, in your test:
return blacklist.isBlacklisted(release, uuid())
.then(() => {
sinon.assert.calledTwice(spy);
});
Also... This isn't related to your problem, but your _spawnPythonProcessGenerator method doesn't need to be a generator. You're only using the first value of it by calling next like that and calling the whole thing over again for each array item.
It will work the same if you take out the *, change yield to return, and skip the .next().value when you call it. You also probably want to rename it because it's not a generator.
_spawnPythonProcess(scriptSrc, name) {
const pythonProcess = this._childProcess.spawn(
'python3',
[...arguments]
);
return new Promise((resolve, reject) => {
pythonProcess.stderr.on('data', (err) => {
reject(err.toString());
});
pythonProcess.stdout.on('data', (data) => {
resolve(data.toString());
});
});
}
When you call it:
let promises = names.map((name) => {
return this._spawnPythonProcess(
this.IS_BLACKLISTED_SCRIPT,
name
)
.then((data) => {
console.log(data);
})
.catch((err) => {
this._errorEvent(release, name, err, jobUUID);
});
}, this);
return Promise.all(promises);

Correct way to cascate promises for arrays (mongoose example)

I need to understand the correct way to cascade promises using mongoose.
My function createCustomerBills with receive a list of customer id's that, for each one, I need to create a simple bill.
Here is the code:
const createCustomerBill = (customer) => {
return BillModel.create({
customer_id: customer.id,
value: 100
});
}
const createCustomerBills => (customerIds) => {
let criteria = {
_id: { $in: customerIds }
};
return CustomerModel.find(criteria)
.then(result => {
return result.map(customer => {
return createCustomerBill(customer);
}
})
.then(result => {
return CustomerModel.update(
{ _id: customer.id },
{ status: "BillCreated" }
);
});
}
Here is the steps:
1. Get list of all customers
2. For each customer, create the bill
3. For each bill created, update the customer status
I need an advice if this is the correct method of doing it and possible drawbacks.
Because you are mapping the result of CustomerModel.find, returning that array wont wait for the promises to complete to run the next .then, as an array is not a Promise
That's where Promise.all comes in
Also, as you need to update each bill individually, that part of the promise chain needs to be inside the .map iteration
const createCustomerBill = customer =>
BillModel.create({
customer_id: customer.id,
value: 100
});
const createCustomerBills => customerIds => {
let criteria = {
_id: { $in: customerIds }
};
return CustomerModel.find(criteria)
.then(result =>
Promise.all(
result.map(customer => createCustomerBill(customer)
.then(result => CustomerModel.update({ _id: customer.id }, { status: "BillCreated" }))
)
)
)
.then(result => {
// all bills are now processed
});
}
Here is a possible working way, IMO
const createCustomerBill = (customer) => {
return BillModel.create({
customer_id: customer.id,
value: 100,
});
}
const createCustomerBills => (customerIds) => {
const criteria = {
_id: {
$in: customerIds,
},
};
return CustomerModel.find(criteria)
.then((customers) => Promise.all(customers.map(x => createCustomerBill(x)
.then(() => CustomerModel.update({
_id: x.id,
}, {
status: "BillCreated"
})))))
.then(() => console.log('over'))
.catch(e => console.log(e));
}
And here using the awesome async/await
const createCustomerBill = customer => BillModel.create({
customer_id: customer.id,
value: 100,
});
const updateCustomer = customer => CustomerModel.update({
_id: x.id,
}, {
status: 'BillCreated',
});
const createBillAndUpdateCustomer = async(customer) => {
await createCustomerBill(customer);
await updateCustomer(customer);
};
const createCustomerBills => async(customerIds) => {
const customers = await CustomerModel.find({
_id: {
$in: customerIds,
},
});
await Promise.all(customers.map(x => createBillAndUpdateCustomer(x)));
console.log('over');
}

Categories

Resources