Can't access element of Array - arrays

I'm adding elements to an array using a service, which successfully adds the elements. I can see the data populated with a console.log
I can't however access the element.
this.routingService.getNode(startNode).subscribe(node => {
node.forEach(node => {
this.openSet.push(new MapNode(node.id, node.lon, node.lat, this.routingService.getNodeNeighbours(node.id)));
});
});
console.log(this.openSet); // this prints out the below screenshot
However, when I use something like:
console.log(this.openSet[0]);
I get output of 'undefined'. I'm not sure if I'm being really thick right now in how I'm accessing it or not...
Any ideas?

subscribe works asynchron, so console.log() will log before the forEach loop runs. It's the same async behaviour as in this little piece of code
let foo = [];
setTimeout(() => {
foo.push(1);
foo.push(2);
}, 500);
console.log(foo); // logs []
Please see the duplicate post for options on how to work with asynchronity.
How to return the response from an asynchronous call?

Related

Array empty even after a loop

I'm trying to fill an array with some registers from a database. However, even after retrieving said registers and place them into the array, after the loop the array stays empty
const userParties = [];
userFollowing.forEach(element => {
// Here's when I gather the information that I need
dispatchGetUserEvents(element.user, response => {
userParties.push(response);
console.log(userParties); // Here the array is full of elements, like it's supposed to be
});
});
console.log(userParties); // However, here 'userParties' return '[]'
this.setState({ followUsersEvents: userParties });
this.setState({ userImage: userImg });
I tried to update the state array on the loop, but I had no luck there either.
'userParties' is not a state array btw.
const userParties = [];
userFollowing.forEach(element => {
// Here's when I gather the information that I need
dispatchGetUserEvents(element.user, response => {
userParties.push(response);
console.log('dispatchGetUserEvents', userParties); // Here the array is full of elements, like it's supposed to be
});
});
console.log('outside', userParties); // However, here 'userParties' return '[]'
this.setState({ followUsersEvents: userParties });
this.setState({ userImage: userImg });
run this code and you will see that outside will be printed first and dispatchGetUserEvents later, as I mentioned in comments dispatchGetUserEvents is async function, so first will be executed console.log('outside', ...);

Array is undefined when trying to load data into Array

Im trying to load data into an array and just added soem console.log to test if it works.
reload(){
this.dataService.getPostoffice()
.subscribe(data => this.poArray= data);
console.log(this.poArray);
}
Sadly the array is undefined at this point. I mean im filling the data into the array there but its still undefined. If I try to console.log that array later in the code its working fine and logs the full data of the array. Can someone help me? :)
You are filling the array asynchronously that is why the array is filled but you are not displaying it at a correct time.
reload(){
this.dataService.getPostoffice()
.subscribe(data => {
this.poArray= data;
console.log(this.poArray);
});
}
Here you nee to have the console inside the subscribe like the snippet below:
reload(){
this.dataService.getPostoffice()
.subscribe(data => {
this.poArray= data;
console.log(this.poArray);
});
}
Hope this helps.

Merging observables and get the stream in one subscribe

I have an end point that returns a list of favorites, then when this list returns i get each of these favorites and send to another endpoint to get the specific information of each of these favorite:
this.favoriteData = [];
const observables = [];
favoriteList.forEach(favorite => {
observables.push(this.assetService.getAsset(favorite.symbol)
.pipe(takeWhile((response: AssetModel) => response.id !== 0)));
});
merge(...observables)
.subscribe(res => {
this.favoriteData.push(res);
this.showLoading = false;
});
As you can see the getAsset() function calls an endpoint, and it is inside an forEach, and im saving each of these response inside an array and spread this array inside a merge function of RXJS, but when i subscribe to the merged observable and append the response to the array favoriteData, the subscribe function behavior is like, binding one by one of the response data:
i want a behavior that waits all the requests to complete and then get all of the responses in one stream, im trying to use the forkJoin operator but the tslint tells that is deprecated and to use map operator, and i dont know how to use it to do what i want
You could use a combineLatest instead, which waits for all observables to complete before emitting.
combineLatest(...observables)
.subscribe(res => {
this.favoriteData.push(res);
this.showLoading = false;
});
You can use from to generate an Observable which emits each element of the base array one at a time. Then, using mergeMap and toArray, you emit all required data at the end:
const favoriteData$ = from(favoriteList).pipe(
mergeMap(favorite => this.assetService.getAsset(favorite.symbol)
.pipe(takeWhile((response: AssetModel) => response.id !== 0))),
toArray()
);

Superagent Not Returning Value From Then

superagent.get(URL).then((res) => {
for(let i in res.body) {
if (i==='has_rejected_advisories') {
console.log(i + "="+res.body[i]);
}
}
})
.catch((err) => err.message));
My result is:
has_rejected_advisories=false
But I am not able to use res.body[i] outside this function, i.e I want superagent function to return this value in a boolean variable to use it elsewhere.
ex.
a = superagent.get(URL).then((res) => {
for(let i in res.body) {
if(i==='has_rejected_advisories') {
console.log(i + "="+res.body[i]);
}
}
})
.catch((err) => err.message));
if(a===false){/*do this*/}
This is because the superagent.get(url) call is asynchronous. The value given to a is a Promise
Since this is async, the if (a === false) is actually executing before the function body passed to .then. You will either need to move this logic to the .then function, or use something like async/await if you like the synchronous looking syntax.
On top of jerelmiller's great advice you need to note the following:
Try this:
create a global var assuming it's a string
var mysares = ""
This example will only bring back 1 string back of everything!! Not single element. Also if you can't get the standard Fetch() to work don't try other methods like axios or superagents. Now use our global like so:
superagent.get(URL).then((res) => {
for(let i in res.body) {
if (i==='has_rejected_advisories') {
//Add comments as will help you
//to explain to yourself and others
//what you're trying to do
//That said if this iteration produces
//correct data then you're fine
//push my result as a string
mysares = res.body[i];
//infact what's in row 1?
mysares = res.body[0];
//Actually I code my own JSON!!
mysares = res.body[1];
console.log(i + "="+mysares);
}
}
})
.catch((err) => err.message));
Now you can do whatever:
if(mysares===false){/*do this*/
alert(playDuckHunt());}
Things to note:
res.body[i] is an iteration
You cannot use it outside of the function
Because:
It's local to that function
You don't know what position of 'i' is even if you could use it as you will be outside of your loop
One last thing:
Loops loop through loops or arrays etc.
So (in real world) you can't just request the value of the loop
unless you agree the position of data to be released,
type,and bucket (where it's going to be displayed or not).
Hope this helps!
PS> we need to know where 'has_rejected_advisories' is in the JSON so send us your json url as it must be a column/obj header name. Or it's any old 'a' then var a can be your "false"
In constructor:
this.state = {a:null};
In some function:
superagent.get(URL).then(
(res) => {for(let i in res.body)
{
if(i === 'has_rejected_advisories')
{
this.setState({a:res.body[i]})
}
}
}).catch((err)=>(err.message));
In render:
console.log(this.state.a);
Inside then() the value could be used using state variable but there are many scenarios we could not use them, like if we want to perform all the operations under constructor i.e Initializing state variable, calling superagent and changing the state variable and using the state variable.

Re-subscribe to an observable with rx-angular

I'd like to use angular-rx for a simple refresh button for results. If the user clicks the refresh button the results are reloaded. If the user clicks the the refresh button 100times in 1 second, only the latest results are loaded. If the results failed for some reason, that doesn't mean the refresh button should stop working.
To achieve the last point I'd like to keep a subscription (or resubscribe) even if it fails, but I can not work out how to do that?
This doesn't work, but here's a simple example where I try resubscribing on error:
var refreshObs = $scope.$createObservableFunction('refresh');
var doSubscribe = function () {
refreshObs
.select(function (x, idx, obs) {
// get the results.
// in here might throw an exception
})
.switch()
.subscribe(
function (x) { /*show the results*/ }, // on next
function (err) { // on error
doSubscribe(); // re-subscribe
},
function () { } // on complete
);
};
doSubscribe();
I figure this is common enough there should be some standard practice to achieve this?
UPDATE
Using the suggested solution, this is what I've made to test:
// using angularjs and the rx.lite.js library
var testCount = 0;
var obsSubject = new rx.Subject(); // note. rx is injected but is really Rx
$scope.refreshButton = function () { // click runs this
obsSubject.onNext();
};
obsSubject.map(function () {
testCount++;
if (testCount % 2 === 0) {
throw new Error("something to catch");
}
return 1;
})
.catch(function (e) {
return rx.Observable.return(1);
})
.subscribe(
function (x) {
// do something with results
});
And these are my test results:
Refresh button clicked
obsSubject.onNext() called
map function returns 1.
subscribe onNext is fired
Refresh button clicked
obsSubject.onNext() called
map function throw error
enters catch function
subscribe onNext is fired
Refresh button clicked
obsSubject.onNext() called
Nothing. I need to keep subscription
My understanding is that catch should keep the subscription, but my testing indicates it doesn't. Why?
Based on the context given in your comment, you want:
Every refresh button to trigger a 'get results'
Every error to be displayed to the user
You really do not need the resubscribing, it's an anti-pattern because code in Rx never depends on that, and the additional recursive call just confuses a reader. It also reminds us of callback hell.
In this case, you should:
Remove the doSubscribe() calls, because you don't need them. With that code, you already have the behavior that every refresh click will trigger a new 'get results'.
Replace select().switch() with .flatMap() (or .flatMapLatest()). When you do the select(), the result is a metastream (stream of streams), and you are using switch() to flatten the metastream into a stream. That's all what flatMap does, but in one operation only. You can also understand flatMap as .then() of JS Promises.
Include the operator .catch() which will treat your error, as in a catch block. The reason you can't get more results after an error happens, is that an Observable is always terminated on an error or on a 'complete' event. With the catch() operator, we can replace errors with sane events on the Observable, so that it can continue.
To improve your code:
var refreshObs = $scope.$createObservableFunction('refresh');
refreshObs
.flatMapLatest(function (x, idx, obs) {
// get the results.
// in here might throw an exception
// should return an Observable of the results
})
.catch(function(e) {
// do something with the error
return Rx.Observable.empty(); // replace the error with nothing
})
.subscribe(function (x) {
// on next
});
Notice also that I removed onError and onComplete handlers since there isn't anything to do inside them.
Also take a look at more operators. For instance retry() can be used to automatically 'get results' again every time an error happens. See https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/retry.md
Use retry() in combination with do() in order to handle the error (do), and allow the subscriber to automatically resubscribe to the source observable (retry).
refreshObs
.flatMapLatest(function (x, idx, obs) {
// get the results.
// in here might throw an exception
// should return an Observable of the results
})
.do(function(){}, // noop for onNext
function(e) {
// do something with the error
})
.retry()
.subscribe(function (x) {
// on next
});
See a working example here: http://jsfiddle.net/staltz/9wd13gp9/9/

Resources