如何在Angular 2/4 Typescript中按顺序运行函数

时间:2017-08-06 15:34:13

标签: angular typescript asynchronous

我无法在Angular 2/4项目中按顺序运行函数。

我有retrieveData()函数从数据服务中检索数据并将它们分配到我声明的数组中。

然后我有displayData()函数,它使用存储在数组中的数据并将它们显示在图表中。

当我尝试运行它们时,例如:

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

displayData()函数首先在retrieveData()之前运行,主要是因为retrieveData()函数中的数据服务。因此无法正确显示图表。

我发现可以按顺序运行函数的方法之一是来自async.waterfall库的async但是我无法将库导入到我的项目中,控制台日志说: Uncaught Error: Unexpected value 'waterfall' imported by the module 'AppModule'. Please add a @NgModule annotation.

我不想使用Promises和Observables,因为它们需要初始函数才能将某种返回值传递给下一个函数。我有点设法使用setTimeOut()函数实现它,但我真的怀疑该方法的可靠性和稳健性。

那么,在Angular 2/4中使用async的任何帮助,或任何一种使函数等待而没有任何返回承诺的方法?

更新

对于造成困难和不便的人抱歉。我发帖并要求过度简化的版本。这是我的代码中更完整的部分。我是一个Angular和Typescript noob,在涉及异步编程技术时更多的菜鸟。

以下是我在retrieveAllData()方法中实现的承诺。它在编译时或运行时都没有给出任何错误。但是当函数仍然异步运行时,refreshAllCharts()仍然在retrieveAllData()之前运行。我的承诺是否有任何缺陷?

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
        }
      }]
    }
  };
}

5 个答案:

答案 0 :(得分:3)

使用promise时,您不必返回任何值以传递给下一个函数。 如果你想保持函数签名不变(即retrieveData()displayData()不接受任何参数并返回void)请考虑使用这样的promises:

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()});
  }

我建议使用promise wrapper作为强大的链接构造来序列化异步操作

答案 1 :(得分:2)

在您的更新旁边,您可以链接承诺。首先获取数据,并在数据就绪时刷新graphe。此链接可以帮助您更好地了解承诺: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 :(得分:1)

有一个很棒的网站解释async concepts in rxjs terms,我发现它非常方便,因为rxjs文档是用非常复杂的术语编写的。

他们认为这等同于异步瀑布函数:

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; 
        });
    }
}

然后使用fromCallback作为序列中的第一个,并在连续调用中使用fromNodeCallback将结果链接到序列中。

答案 3 :(得分:1)

尝试这样的事情:

  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();
          })
  }

retrieveData函数通过dataService获取数据并返回一个promise。

你调用executeSerialFunctions它将调用retrieveData,当fetch完成并且数据已经返回时,它将继续处理数据,就像代码中的retrieveData函数一样(使用响应迭代器)。

一旦完成,它将返回响应,并且.then将执行 - 它有效地响应响应,但在处理完所有数据后将调用displayData。

我无法测试这一点,但我认为这应该可行。

答案 4 :(得分:0)

今天,现代的javascript架构不是同步的,而是asynchrone,以获得更好的ui体验......&#39;我的意思是启动了http get请求,在此期间,您不会等待数据准备就绪,而是继续执行您的程序。一旦您的数据准备就绪(xhr请求完成),您将收到通知并能够使用它们。 您必须实现一个检索数据的角度服务,并使用observable。

基本上是您的视图的示例代码,它调用负责http请求的服务

    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 */ 
              });
          });
     } 
  }

这是我的Http服务:

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();
      });    
    }
}