5

I am having trouble trying to run functions sequentially in my Angular 2/4 project.

I have retrieveData() function that retrieves data from data service and assigns them into an array that I declared.

Then I have displayData() function that uses the data stored in the array and display them in a chart.

When I try to run them, for eg:

function(){
  this.retrieveData();
  this.displayData();
}

displayData() function runs first before retrieveData() mainly because of the data service in the retrieveData() function. Therefore the graph cannot be displayed properly.

One of the ways that I found that I can run functions sequentially is async.waterfall from async library but I am having trouble importing the library into my project, with the console log saying: Uncaught Error: Unexpected value 'waterfall' imported by the module 'AppModule'. Please add a @NgModule annotation.

I don't want to use Promises and Observables because they require the initial function to have some kind of return value to pass on to the next functions. I somewhat managed to achieve it using setTimeOut() function but I really doubt the reliability and robustness of the approach.

So, any help with the use of async in Angular 2/4, or any kind of ways to make the functions wait without any kind of return promises?

UPDATE

Sorry for the confusion and inconvenience caused guys. I was posting and asking for an over-simplified version. Here is the more complete part of my code. I am an Angular and Typescript noob, and more noob when it comes to Async programming techniques.

Below is my implementation of promises in retrieveAllData() method. It doesn't give any error in either compile time or runtime. But when the functions are still running asynchronously, i.e. refreshAllCharts() still runs before retrieveAllData(). Is there any flaws in my implementation of promises?

import { Component, OnInit, AfterContentInit } from '@angular/core';
import { DataService } from '../data.service';
import {BaseChartDirective} from 'ng2-charts/ng2-charts';
import {IMyDpOptions,IMyDateModel} from 'mydatepicker';

//import {MomentTimezoneModule} from 'angular-moment-timezone';
import * as moment from 'moment-timezone';

// import async from 'async-waterfall';

@Component({
  templateUrl: 'chartjs.component.html'
})
export class ChartJSComponent {

  tempArr = []; //array to store temperature values for the chart
  timeTempArr = []; //array to store timestamps for the chart label

  device = "1CB001"; //a parameter used for the data service method to query the database

  dateSelected; //variable to store the date chosen from the datepicker on the html side of the component

  constructor(private dataService: DataService){
  }

  ngOnInit(){
  }

//function to retrieve temperature values and assign them into "tempArr" array
  retrieveTempDataAssign(){
    var _arr = new Array();

    this.dataService.getData(this.device, this.dateSelected).subscribe(response => {

      console.log("Response: " + JSON.stringify(response));
      for(var item of response){
        _arr.push(item.Temperature);
      }

      this.tempArr = _arr;
      console.log("Array assigned Temp: " + this.tempArr);
    });

    this.retrieveTempTimeDataAssign();

  }

//function to retrieve time values and assign the date and time objects into "timeTempArr" array
  retrieveTempTimeDataAssign(){

    var _arr = new Array();

    this.dataService.getData(this.device, this.dateSelected).subscribe(response => {

      for(var item of response){
        // var value = "'" + item.Date + "'";
        // _arr.push(value);

        var value = item.Date;
        var time = moment.tz(value, "Asia/singapore");
        _arr.push(time);
      }
      this.timeTempArr = _arr;
      console.log("Array assigned Time: " + this.timeTempArr);
    });
  }

//function to refresh the whole of Temperature chart
  refreshTempChart(){
    this.showTempData();
    setTimeout(() => this.showTempLabels(), 500);
  }

//function to assign the "tempArr" array into the dataset for the temperature chart
  showTempData(){
    console.log("To display: " + this.tempArr);
    this.datasetsTemp = [{
      label: "Values",
      data: this.tempArr
    }];
  }

//function to assign the "timeTempArr" array into the labels for the temperature chart
  showTempLabels(){
    console.log("To label: " + this.timeTempArr);
    this.labels = this.timeTempArr;
  }

//date picker format
  private myDatePickerOptions: IMyDpOptions = {
        dateFormat: 'yyyy-mm-dd',    
  };

//change event listener on the datepicker
  onDateChanged(event: IMyDateModel){

    this.dateSelected= event.formatted;
    console.log("Selected Date: " + this.dateSelected);

//**The implementation part**
    this.retrieveAllData().then(()=>{
      this.refreshAllCharts();
    })

  }

//to run all functions to retrieve respective data
  retrieveAllData(){
    return new Promise((resolve, reject) => {
      this.retrieveTempDataAssign(); //assign the retrieved values into the array first

      return true;
    });
  }

//to run all functions to update all the charts
  refreshAllCharts(){
    this.refreshTempChart();
  }

//objects used by the chart to display data
  private datasetsTemp = [
    {
      label: "Values",
      data: []
    }
  ];

  private labels = [];

  private options = {
    scales: {
      xAxes: [{
          display: true,
          type: "time",
          time: {
              unit: "hour",
              tooltipFormat: 'YYYY-MM-DD hh:mm A'
          },
          scaleLabel: {
              display: true,
              labelString: 'Time'
          }
      },],
      yAxes: [{
        ticks: {
          beginAtZero: false
        }
      }]
    }
  };
}
9
  • 1
    they require the initial function to have some kind of return value to pass on to the next functions. - and that's the correct approach. async.waterfall does exactly that Commented Aug 6, 2017 at 15:39
  • @Maximus In my approach, the methods don't return anything. They just assign the retrieved values to their own respective arrays declared outside. So, all I need is to make them run in order without passing any values to each other. How? Commented Aug 6, 2017 at 15:42
  • 1
    Use promises or async/await Commented Aug 6, 2017 at 15:53
  • 1
    Promises are the way to accomplish this. Your function doesn't have to return a value from anything in particular, you can just return 'true' when one function completes and feed that into the next function in the chain, but that gets you the this .then .then .finally structure you need to accomplish what you're looking for. Commented Aug 6, 2017 at 16:19
  • try using flatMap service=>flatMap=>displayData() Commented Aug 6, 2017 at 16:22

5 Answers 5

3

You don't have to return any value to pass on to the next functions while using promise. If you want to keep you function signature untouched (i.e. retrieveData() and displayData() do not take any parameters and return void) consider using promises like this:

private dataStorage: string = null;
private retrieveDataResolver;

  displayData(): void {
    // your display code goes here
    console.log("2. DISPLAYING DATA", this.dataStorage);
  }
  retrieveData(): void {
    // your async retrieval data logic goes here
    console.log("1. GETTING DATA FROM SERVER");
    setTimeout(() => { // <--- Change it - your service data retrieval
      this.dataStorage = '++DATA++';
      this.retrieveDataResolver(); // <--- This must be called as soon as the data are ready to be displayed
    }, 1000);
  }

  retrieveDataPromise(): Promise<any> {
    return new Promise((resolve) => {
      this.retrieveDataResolver = resolve;
      this.retrieveData();
    })
  }
  retrieveAndThenDisplay() {
    this.retrieveDataPromise().then(() => {this.displayData()});
  }

I recommend using promise wrapper as powerful chaining construct to serialize asynchronous operations

Sign up to request clarification or add additional context in comments.

3 Comments

Thank you for your idea. I followed it and still not working. They are still running asynchronously even though I've already implementing promises. link Is there any flaws in the way I implemented the promises?
If the calls from your example (this.retrieveTempDataAssign(); ...) are asynchronous as well you should call resolve(); in successful callback of these calls. See my example - I call resolve inside setTime function (which is just replacement for some http async call)
If you need synchronise this.retrieveTempDataAssign(); this.retrieveHumiDataAssign(); this.retrieveLightDataAssign(); this.retrieveCO2DataAssign(); you can use flatMap to do so (If it returns observable)
2

Next to your update, you can chain promise. first get data, and refresh graphe called when data ready. this link may help you better understand promises : https://codecraft.tv/courses/angular/es6-typescript/promises/

// get data
var job1 = new Promise(function(resolve, reject){
    resolve('done1');
});


job1.then(function(data) {
    refreshGraphe();
})

2 Comments

Thank you for your idea. I followed it and still not working. They are still running asynchronously even though I've already implementing promises. link Is there any flaws in the way I implemented the promises?
it's normal http requests are launched and you resolve() the promise immediately. the getData call must be inside the promise() . have a look at @kuceraf sample code. he backups resolve() func in this.retrieveDataResolver variable and call it once the response has arrived (inside subscribe when you use results of http requests). and you have done all when job1.then(()=>job2.then(()... => refresh_chart()));
1

There is a great site that explains async concepts in rxjs terms, I've found it very handy as the rxjs docs are written in very complex terminology.

They suggest this as equivalent to the async waterfall function:

var Rx = require('rx');

var async = {
    waterfall: series => {
        return Rx.Observable.defer(() => {
            var acc = series[0]();
            for (var i = 1, len = series.length; i < len; i++) {

                // Pass in func to deal with closure capture
                (function (func) {

                    // Call flatMapLatest on each function
                    acc = acc.flatMapLatest(x => func(x));
                }(series[i]));
            }

            return acc; 
        });
    }
}

You then use fromCallback for the first in the sequence and fromNodeCallback on the successive calls to chain the results through the sequence.

Comments

1

Try something like this:

  retrieveData(): Promise<any> {
      return this.dataService.getData(this.device, this.dateSelected)
          .map(response => response.json())
          .toPromise();
  }


  executeSerialFunctions() {
      this.retrieveData()
          .then(response => {
              for(var item of response){
                var value = item.Date;
                var time = moment.tz(value, "Asia/singapore");
                _arr.push(time);
              }
              this.timeTempArr = _arr;
              console.log("Array assigned Time: " + this.timeTempArr);
              return response 
          }).then(response => {
              displayData();
          })
  }

The retrieveData function fetches the data via the dataService and returns a promise.

You call executeSerialFunctions and it will call retrieveData and when that fetch is complete and the data has returned, it will proceed and process the data the way you had inside the retrieveData function in your code (with the response iterator).

Once that's complete, it returns the response, and the .then will execute - it effectively eats the response, but will call displayData after all the data has been processed.

I can't test this, but I think this should work.

1 Comment

Thanks for your idea, but I'm using observable in the retrieval method.
0

Today, modern javascript architecture is not made to be synchrone, but asynchrone, 'for a better ui experience...'. I mean the http get request is launched, during this time, you don't wait for data ready, but continuing execution of your program. Once your data are ready (xhr request done), you are notified and able to use them. you have to implement an angular service that retrieves the data , and using observable.

basically a sample code for your view , that calls the service responsible for http requests

    import { Component, OnInit } from '@angular/core';
    import { IntervalObservable } from "rxjs/observable/IntervalObservable";
    // my data model
    import { PositionsModel } from "./positionsmodel";
    // the service responsible to make the http requests
    import { MouvementService } from './mouvement.service';

    @Component({
      selector: 'app-mouvementview',
      template: '<div  *ngIf="data">  
                <div>{{data.x1r'}} </div>
                </div> ',
      styleUrls: ['./mouvementview.component.css']
    })

    export class MouvementviewComponent implements OnInit {
      public data: PositionsModel;
      private display : boolean;

      // Inject mouvementService
      constructor(private mouvementService: MouvementService) {
        this.display = false;
        this.alive = true;
        }

      ngOnInit() {
          this.mouvementService.getPositions()
          .first() // only gets fired once
          .subscribe((data) => {
            // this part of code will execute only when data will be retrieved. 
            this.data = data;
            this.display = true;
            /* Refresh your chart , NOTE : chart refresh will be done itself , if you are using some components with databing */
          });

     // if you need periodic refresh 
     // get our data every subsequent 200 mseconds
        IntervalObservable.create(200)
          .takeWhile(() => this.alive) // only fires when component is alive
          .subscribe(() => {
            this.mouvementService.getPositions()
              .subscribe(data => {
            console.log(data);
            this.data = data;
            /* refresh your chart */ 
              });
          });
     } 
  }

here is my Http service :

import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';
import { Observable } from "rxjs";
import 'rxjs/Rx';
import { PositionsModel } from "./positionsmodel";



@Injectable()
export class MouvementService      {

constructor(private http: Http) {}

getPositions(): Observable<PositionsModel> {
    // Make the HTTP request:
    console.log("launching http get request");
    return this.http
      .get('http://192.168.1.170:8080/api/mouvements')
      .map((res: Response) => {
        return res.json();
      });    
    }
}

4 Comments

This is arguably the worst Answer I've ever seen in 6+ years on StackOverflow.
This is the tendance. Most of the browsers will not allow sync http requests, scripts are loaded in parallel. why don't you agree?And that's the way you will have to program, using promises.
You are stating the blatantly obvious here, and providing ZERO solution to the question. You might as well add that TypeScript adds classes to JavaScript while you're at it.
This still doesn't address the question about sequentially running HTTP calls. Sigh.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.