0

I am using Angular 2 for creating a webapp and ran into a weird problem which is worth understanding.

I am trying to print the value of an object right after assigning a new value and a bit later. Following is the code:

do {
    this._sharedService.readServerStatus().subscribe(res =>
      {
        this.surveyStatus = JSON.parse(JSON.stringify(res));
        console.log(this.surveyStatus);
    });
    console.log("ne");
    console.log(this.surveyStatus);
  }
  while(this.surveyStatus.isBusy());

In this code, surveyStatus is an object which I wish to print to the console. Following is the output from the browser console:

Object {serverBusy: 1, terminate: 0}
ServerStatus {}

The first one is printed out as expected, while when I read it outside the loop, something weird happens to the object. Can someone help me understand what's going on.

EV_A
  • 108
  • 1
  • 7
  • Try `console.table([obj])` instead of `console.log(obj)` to see it freezed. – Qwertiy Aug 23 '17 at 09:20
  • That may not help my purpose. I found that when I read the object outside the subscribe, it is not assigned yet. So I am looking for a way to ensure I wait until the process is over, or an alternative for subscribe. – EV_A Aug 23 '17 at 09:32
  • You are doing a wrong thing. There is nly one thread and you need only one subscribe. And you have to wait until callback is executed. – Qwertiy Aug 23 '17 at 09:33

2 Answers2

1

The problem is that your readServerStatus() call is async and therefore will print the value when it's emitting a value. But the rest of your code doesn't wait for it, so your second console.log prints an empty object.

But there's a huge problem with your code, your generating tons of subscriptions inside your while loop without completing them, which will lead to a memory leak.

I would suggest to use the repeatWhile operator with takeUntil of RxJs for such a task.

Here's an example:

let mockResponse = { serverBusy: 1, terminate: 0 };
const request = () => {
  return Rx.Observable.of(mockResponse).delay(500); // emulate api call
};

request()
  // repeat the request while isBusy is true
  .repeatWhen(notifications => {
    return notifications.takeWhile(() => {
      return !!mockResponse.serverBusy;
    });
  })
  .subscribe(res => {
    console.log(res);
  });

setTimeout(() => {
   mockResponse.serverBusy = 0;
}, 2000);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.4.3/Rx.min.js"></script>

Here's another example using switchMap to read the data from the response and decide if you need to retry, if not emit the response.

let mockResponse = { serverBusy: 1, terminate: 0 };
const apiCall = () => {
  return Rx.Observable.of(mockResponse).delay(500); // emulate api call
}

function makeRequest$() {
  let request$ = apiCall()
    .switchMap(res => {
      console.log('is busy:', !!res.serverBusy);
      return !!res.serverBusy ? request$ : Rx.Observable.of(res);
    });
  return request$;
}

makeRequest$().subscribe(res => {
  console.log(res);
});

setTimeout(() => {
   mockResponse.serverBusy = 0;
}, 2000);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.4.3/Rx.min.js"></script>
cyr_x
  • 13,987
  • 2
  • 32
  • 46
  • ^ yes what this guy said. If its not generating a compile or runtime error, that means its just looping probably hundreds of times before it even gets the first response from the observable. I'm somwhat surprised it runs without error though, because this.surveyStatus.isBusy() doesn't appear to be defined anywhere until the subscription is received. I figured it would have thrown an error, unless it has been instantiated previously. – diopside Aug 23 '17 at 09:56
  • This is exactly what I was looking for. I am a novice in this field. So, in my case the function `request()` returns a json object. Can you also suggest how I can read the value of the return object inside `repeatWhen`. Basically, what I am trying to do is wait until the server finish computation and update it's status in a database. – EV_A Aug 23 '17 at 10:02
  • Added a second example to my answer. – cyr_x Aug 23 '17 at 10:27
0

it's happening because the console print outside the subscription will not wait for the new value assignment. It will be printed before new value arrives and assings this.surveyStatus = JSON.parse(JSON.stringify(res));. So while you are subscribing, if you wanted to do anything on value changes, put whole thing inside the subscription.

Rajez
  • 3,717
  • 1
  • 14
  • 21
  • Thank you for the response. I have a function which looks like this. readServerStatus(){ return this._http.get('http://localhost:3000/getServerStatus') .map((res: Response) => { return res.json() }); } I was wondering how to get the return value into an object, if not using subscribe. Sorry for not intending the comment. – EV_A Aug 23 '17 at 09:28
  • We can't get the value without subscription. The thing you did everything is okay, but why do you need to access the value of `this.surveyStatus` before it's get updated?. – Rajez Aug 23 '17 at 09:33
  • I am not trying to access the value before it gets updated. The problem was that the do while loop exits after a single loop. isBusy() is a function to just read the value of an object element. This was the reason why I added the print outside subscribe. – EV_A Aug 23 '17 at 09:37