我无法在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
}
}]
}
};
}
答案 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();
});
}
}