I have implemented a function that I use to call an api, recover for each product some info and push into array. After that I have done this for all products, I would to do a action with the array.
So I have:
newProducts: any = []
loadOfferRelated(offer) {
// consider this offer with 2 products array.
let products = offer.products
for (let i = 0; i < products.length; i++) {
let apiCall = this.offerService.apiCall(products[i].id, product.catalogId)
apiCall.pipe(untilDestroyed(this)).subscribe(
(data) => { operationOnArray(data, products[i])}
)
}
if(this.newProducts.length > 0){
// ---> Here i should call another function but it doesn't enter there)
}
operationOnArray(data, product){
// I make some operation product and save in array
this.newProducts.push(products)
console.log("this.newProducts", this.newProducts) <-- this is every for populated + 1
return
}
I have a problem to call the if when the array newProducts is populated, how can I do?
Ok so you can use a forkJoin, to make the api calls simultaneously and use the map operator to return each transformed element, push the returned data into the array and then finally call the if condition.
newProducts: any = [];
loadOfferRelated(offer) {
// consider this offer with 2 products array.
let products = offer.products;
const apiCalls = products
.map((product: any) => this.offerService.apiCall(product.id, product.catalogId))
.pipe(
map((data: any, i: number) => {
// i am not sure what your doing with the api call (data) so I am merging product and data, you customize
// to your requirement
return { ...products[i], ...data };
})
);
forkJoin(apiCalls).subscribe((newProducts: Array<any>) => {
this.newProducts = newProducts;
if (this.newProducts.length > 0) {
// ---> Here i should call another function but it doesn't enter there)
}
});
}
Your problem is that whatever happens in you subscription is asynchronous. That means that your if is triggered before your request has ended, so it's most likely that your if condition will always end with a false.
Related
I am coding for my React Native app and I am having trouble getting the data from the firebase return outside of the firebase.firestore().collection("test_data").doc(ID) loop. Whenever I check the dataArray variable after the loop it is empty. If I check it within the loop, the data is there. I think that it is a scope problem, but I just do not understand it. I also cannot call any user defined functions inside of the loop.
try {
let dataArray = [];
// get the document using the user's uid
firebase.firestore().collection("users").doc(uid).get()
.then((userDoc) =>{
// if the document exists loop through the results
if (userDoc.exists) {
data = userDoc.data().saved_data; // an array store in firebase
data.forEach(ID => { // loop through array
firebase.firestore().collection("test_data").doc(ID).get()
.then((doc) => {
dataArray.push(doc.data().test_data);
console.log(dataArray) // the data shows
})
console.log(dataArray) // the data does not show
})
}
})
}
catch (error) {
}
}
You're looping through asynchronous calls, so your final console.log will trigger before the data has been received. Your first console.log only triggers after the data has been received.
So the code is working, but the function (promise) will resolve (as undefined, or void) before all the data has been received from your firebase calls.
If you want to return the array to the caller, you could do something like this:
function getDataFromServer() {
// get the document using the user's uid
return firebase.firestore().collection('users').doc(uid).get().then(userDoc => {
// now we're returning this promise
const dataArray = []; // can move this to lower scope
// if the document exists loop through the results
if (userDoc.exists) {
const savedData = userDoc.data().saved_data; // an array store in firebase
return Promise.all(
// wait for all the data to come in using Promise.all and map
savedData.map(ID => {
// this will create an array
return firebase.firestore().collection('test_data').doc(ID).get().then(doc => {
// each index in the array is a promise
dataArray.push(doc.data().test_data);
console.log(dataArray); // the data shows
});
})
).then(() => {
console.log(dataArray);
return dataArray; // now data array gets returned to the caller
});
}
return dataArray; // will always be the original empty array
});
}
Now the function returns the promise of an array, so you could do...
const dataArray = await getDataFromServer()
or
getDataArrayFromServer().then(dataArray => {...})
I need to perform a sequence of http requests to my database from Angular. In the first step I get an array of objects. This is working fine.
In a second step I want to populate each object with additional data. So I need to wait for the first http.request to complete. I also don't want to return before my data has been fetched.
My function getData() should return an Observable, because the interface / signature is not supposed to change (as it is being used in other parts of the program and an observable makes sense for the program logic).
// Returns an array with objects, that have an id, that is needed for the second call
data[] : Object = new Array();
populatedData[] : Object = new Array();
getData(): Observable<Object[]>{
this.http.get<Object[]>("http://localhost:3000/data").subscribe(data => this.data = data);
// This needs the data from the first call
for(int i = 0; i < data.length; i++){
this.http.get<Object>("http://localhost:3000/data/"+data[i].id).subscribe(data => populatedData.push(data));
}
return of(populatedData);
}
You can use switchMap (after the initial GET is done, make the other requests) and forkJoin (make several requests and emit when all are completed). Something like this:
getSingleEntry = ({ id }) => this.http.get<Object[]>("http://localhost:3000/data/" + id);
getData = () => this.http.get<Object[]>("http://localhost:3000/data")
.pipe(
tap(data => this.data = data),
switchMap(data => forkJoin(data.map(this.getSingleEntry)));
Promise is an option but another option can be,
// Returns an array with objects, that have an id, that is needed for the second call
data[] : Object = new Array();
populatedData[] : Object = new Array();
getData(): Observable<Object[]>{
this.http.get<Object[]>("http://localhost:3000/data").subscribe((data)=>{
this.data = data;
for(int i = 0; i < data.length; i++){
this.http.get<Object>("http://localhost:3000/data/"+data[i].id).subscribe((response)=> {
populatedData.push(response)
});
}
});
return of(populatedData);
}
You should put the code that needs the first call in the second
data: Array<any> = [];
populatedData: Array<any> = [];
getData(): Observable<Object[]> {
this.http.get<Object[]>('http://localhost:3000/data').subscribe(data => {
this.data = data;
for (let i = 0; i < data.length; i++) {
this.http
.get<Object>('http://localhost:3000/data/' + data[i].id)
.subscribe(data => populatedData.push(data));
}
});
return of(populatedData);
}
I think the best idea is to use switchMap RXJS operator and zip. In second calls you have access to data from first one. Then you can return array of values when all calls are done.
More about switchMap you can read here. About zip here.
Here you have example how data will looks like https://rxviz.com/v/Mogdv96O
Then your code will be refactored as follow:
getData(): Observable<Object[]> {
this.http.get<Object[]>("http://localhost:3000/data")
.pipe(switchMap((data) =>
zip(...data.map(item => this.http.get<Object>("http://localhost:3000/data/" + item.id)))
)
);
}
the error occurs when the Windows Features are not set up corretly.
You have to enter the -> 'Turn Windows features on or off' from the Windows Search and enter toggle 'HTTP Activation' and is presented on the picture:
Options to toggle
I am trying to get each property of my games within chained promises (Each of the property is coming from a different async calls).
Logic of my algorithm:
Check the Network and Get the smart contract address
Register the contract containing the addresses of all the Games
Get the number of Games
For each game, perform one aSync call
per property
Print all the games and details (here I am not able
to get the updated object)
Code:
var games = [];
window.addEventListener('load', function() {
// Check the Network and assign the smart contract address
web3.eth.net.getId()
.then(function(networkId) {
let contractAddressRegistry;
if (networkId == 1) {
contractAddressRegistry = "0xQWERTYUIOPQWERTYUIOPQWERTY"
} else {
contractAddressRegistry = "0x12345678901234567890123456"
}
return contractAddressRegistry;
})
.then(function(contractAddressRegistry) {
let contractRegistry = new web3.eth.Contract(contractAbiRegistry, contractAddressRegistry);
contractRegistry.methods.numberOfGames().call()
.then(function(numberOfGames) {
for (let i = 0; i < numberOfGames; i++) {
let game = {};
game.propertyA = aSyncCallGetPropertyA(i); // Promise
game.propertyB = aSyncCallGetPropertyB(i); // Promise
game.propertyC = aSyncCallGetPropertyC(i); // Promise
}
games.push(game);
})
})
.then(function() {
console.log(games) // Empty
})
})
I tried used Promises.all() but I am not able to sync it properly as some async calls are within a then().
How can I make sure to get the object Games filled with all its properties?
You should use Promise.all like this. Basically, you need to wrap all three aSyncCallGetProperty async calls in Promise.all for waiting until they really finish then assign the results to object game.
whatever
.then(function(contractAddressRegistry) {
let contractRegistry = new web3.eth.Contract(contractAbiRegistry, contractAddressRegistry);
return contractRegistry.methods.numberOfGames().call();
})
.then(function(numberOfGames) {
return Promise.all(numberOfGames.map(() => {
return Promise.all([
aSyncCallGetPropertyA(),
aSyncCallGetPropertyB(),
aSyncCallGetPropertyC()
]).then(results => {
let game = {};
game.propertyA = results[0];
game.propertyB = results[1];
game.propertyC = results[2];
return game;
});
}));
})
.then(function(games) {
console.log(JSON.stringify(games));
})
#Lewis' code seems right but I can not make sure what numberOfGames is. Assuming that it's an integer as used in your question (not an array as treated in the other answer) here is a further rephrased version without nested .then()s.
window.addEventListener('load', function() {
web3.eth.net.getId()
.then(networkId => networkId === 1 ? "0xQWERTYUIOPQWERTYUIOPQWERTY"
: "0x12345678901234567890123456")
.then(contractAddressRegistry => new web3.eth.Contract(contractAbiRegistry, contractAddressRegistry).methods.numberOfGames().call())
.then(numberOfGames => Promise.all(Array(numberOfGames).fill()
.map(_ => Promise.all([aSyncCallGetPropertyA(),
aSyncCallGetPropertyB(),
aSyncCallGetPropertyC()]))))
.then(function(games){
games = games.map(game => ({propertyA: game[0],
propertyB: game[1],
propertyC: game[2]}));
doSomethingWith(games);
});
});
What my code should do
I am creating a social media app using react native, and I have run into an problem with my code. I am trying to create a function which grabs all of the posts from all of the user's groups. To do this, I created a loop. The loop repeats once for every group the user is in. Every one loop gets the posts from one group. Every time the loop is called, a new function is called which gets the posts from a new group the user is in, then returns the posts back to the original function, which adds them to a full list of posts.
Problem
The function getting the posts doesn't return the posts. I think that the code is not waiting for the posts to be returned, and moves on. Basically, when I console.log the posts on the function that got them, I get the correct posts, but when I console.log the entire list of posts, I get nothing back.
My question
How can I wait for a value to be returned by a function, and not have the code instantly move on?
My code
runQ(group){
//this function actually gets the posts from the server (from firebase)
var items = [];
firebase.database().ref('posts/'+group).limitToLast(
Math.floor(24/this.state.passGroups.length)*this.state.numOfPosts
).orderByKey().once ('value', (snap) => {
snap.forEach ( (child) => {
items.push({
//post info
});
});
this.setState({passItems: items})
console.log(items); //logs correct items.
}).then(()=>{
if( this.state.passItems.length != 0 ){return this.state.passItems;}
})
}
//gets the user's groups, then sends out a request to each group for the newest posts.
getItems(){
//gets groups...
//...
.then(()=>{
var allItems = [];
//allItems will be the big list of all of the posts.
for (i = 0; i < this.state.passGroups.length; i++) {
//sending request to runQ function to get posts.
allItems.push(this.runQ(this.state.passGroups[i].name)) //allItems logs as allItems: ,,,,
}
})
}
Use async-await to make the for loop wait for each response.
First, you need to return the promise created by your Firebase call (you currently don't return anything from your runQ() function).
Change this line:
firebase.database().ref('posts/'+group).limitToLast(
into:
return firebase.database().ref('posts/'+group).limitToLast(
Then tell your callback to the getItems() call to be an async function and await each response from runQ():
getItems(){
//gets groups...
//...
.then(async () => {
var allItems = [];
for (var i = 0; i < this.state.passGroups.length; i++) {
allItems.push(await this.runQ(this.state.passGroups[i].name))
}
})
}
First you have to return a promise from runQ like this: return firebase.database.....
Then in the for loop you can do like this:
let allPromises = []
for (i = 0; i < this.state.passGroups.length; i++) {
allPromises.push(this.runQ(this.state.passGroups[i].name))
}
Promise.all(allPromises).then(itemsArray => {
allItems = allItems.concat(itemsArray)
})
Make sure that you have allItems declared in the right scope.
I'm pushing observables into an array like such...
var tasks$ = [];
tasks$.push(Observable.timer(1000));
tasks$.push(Observable.timer(3000));
tasks$.push(Observable.timer(10000));
I want an Observable that emits when all tasks$ have completed. Keep in mind, in practice, tasks$ doesn't have a known number of Observables.
I've tried Observable.zip(tasks$).subscribe() but this seems to fail in the event that there is only 1 task, and is leading me to believe that ZIP requires an even number of elements in order to work the way I would expect.
I've tried Observable.concat(tasks$).subscribe() but the result of the concat operator just seems to be an array of observables... e.g. basically the same as the input. You can't even call subscribe on it.
In C# this would be akin to Task.WhenAll(). In ES6 promise it would be akin to Promise.all().
I've come across a number of SO questions but they all seem to deal with waiting on a known number of streams (e.g. mapping them together).
If you want to compose an observable that emits when all of the source observables complete, you can use forkJoin:
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/forkJoin';
import 'rxjs/add/operator/first';
var tasks$ = [];
tasks$.push(Observable.timer(1000).first());
tasks$.push(Observable.timer(3000).first());
tasks$.push(Observable.timer(10000).first());
Observable.forkJoin(...tasks$).subscribe(results => { console.log(results); });
You can make usage of zip.
Combines multiple Observables to create an Observable whose values are calculated from the values, in order, of each of its input Observables.
const obsvA = this._service.getObjA();
const obsvB = this._service.getObjB();
// or with array
// const obsvArray = [obsvA, obsvB];
const zip = Observable.zip(obsvA, obsvB);
// or with array
// const zip = Observable.zip(...obsvArray);
zip.subscribe(
result => console.log(result), // result is an array with the responses [respA, respB]
);
Things to consider:
Doesn't need to be an even number of observables.
zip visually
As said here,
The zip operator will subscribe to all inner observables, waiting for each to emit a value. Once this occurs, all values with the corresponding index will be emitted. This will continue until at least one inner observable completes.
When one of the observables throws an error (or even both of them), the subscription is closed (onComplete on complete is called), and with a onError method, you only get the first error.
zip.subscribe(
result => console.log(result), // result is an array with the responses [respA, respB]
error => console.log(error), // will return the error message of the first observable that throws error and then finish it
() => console.log ('completed after first error or if first observable finishes)
);
// waits for all Observables no matter of success/fails each of them
// returns array of items
// each item represent even first value of Observable or it's error
export function waitAll(args: Observable<any>[]): Observable<any[]> {
const final = new Subject<any[]>();
const flags = new Array(args.length);
const result = new Array(args.length);
let total = args.length;
for (let i = 0; i < args.length; i++) {
flags[i] = false;
args[i].subscribe(
res => {
console.info('waitAll ' + i + ' ok ', res);
if (flags[i] === false) {
flags[i] = true;
result[i] = res;
total--;
if (total < 1) {
final.next(result);
}
}
},
error => {
console.error('waitAll ' + i + ' failed ', error);
if (flags[i] === false) {
flags[i] = true;
result[i] = error;
total--;
if (total < 1) {
final.next(result);
}
}
}
);
}
return final.asObservable();
}
unit test:
describe('waitAll', () => {
it('should wait for all observables', async () => {
const o1 = new Subject();
const o2 = new Subject();
const o3 = new Subject();
const o = waitAll([o1, o2, o3]);
const res = {arr: []};
o.subscribe(result => res.arr = result, err => res.arr = []);
expect(res.arr).toEqual([]);
o1.next('success1');
expect(res.arr).toEqual([]);
o2.error('failed2')
expect(res.arr).toEqual([]);
o3.next('success3')
expect(res.arr).toEqual(['success1', 'failed2', 'success3']);
o1.next('success1*');
expect(res.arr).toEqual(['success1', 'failed2', 'success3']);
o2.error('failed2*')
expect(res.arr).toEqual(['success1', 'failed2', 'success3']);
o3.next('success3*')
expect(res.arr).toEqual(['success1', 'failed2', 'success3']);
});
});
For me this sample was best solution.
const source = Observable.interval(500);
const example = source.sample(Observable.interval(2000));
const subscribe = example.subscribe(val => console.log('sample', val));
So.. only when second (example) emit - you will see last emited value of first (source).
In my task, I wait form validation and other DOM event.