【问题标题】:How to handle data comes late from service?如何处理服务迟到的数据?
【发布时间】:2019-04-19 16:45:18
【问题描述】:

在我的 Angular 应用程序中,我需要将数据存储到一个数组中,该数组在初始阶段为空。

示例

someFunction() {

 let array = [];

 console.log("step 1");

 this.service.getRest(url).subscribe(result => { 

   result.data.forEach(element => {

   console.log("step 2");

    array.push(element); // Pushing all the objects comes from res.data     

   });

   console.log("step 3");

 });

   console.log("step 4");

}

这里我列出了console.log() 的步骤顺序。

调用函数时的顺序是,

第 1 步 第 4 步 第2步 第三步

在第 1 步之后,第 4 步调用,然后第 2 步调用。所以如果我 console.log(array) 代替第 4 步,它会再次给出空数组。

但是代替step 2 and 3 它提供了价值。从服务中出来,价值是空的。

因此我总是在array 中得到空值。

即使有一段时间的服务调用和响应返回,请帮助我将数据存储到变量中。

修改代码试了半天还是不行..

编辑

我在下面给出了我目前正在使用的实时应用程序stackblitz链接https://stackblitz.com/edit/angular-x4a5b6-ng8m4z

在此演示中查看文件https://stackblitz.com/edit/angular-x4a5b6-ng8m4z?file=src%2Fapp%2Fquestion.service.ts

我在哪里使用服务调用.. 如果我输入async getQuestions() {},则会给出questions.forEach of undefined 的错误

service.ts

    jsonData: any = [
    {
      "elementType": "textbox",
      "class": "col-12 col-md-4 col-sm-12",
      "key": "project_name",
      "label": "Project Name",
      "type": "text",
      "value": "",
      "required": false,
      "minlength": 3,
      "maxlength": 20,
      "order": 1
    },
    {
      "elementType": "textbox",
      "class": "col-12 col-md-4 col-sm-12",
      "key": "project_desc",
      "label": "Project Description",
      "type": "text",
      "value": "",
      "required": true,
      "order": 2
    },
    {
      "elementType": "dropdown",
      "key": 'project',
      "label": 'Project Rating',
      "options": [],
      "order": 3
    }
  ];

  getQuestions() {

    let questions: any = [];

    //In the above JSON having empty values in "options": [],

    this.jsonData.forEach(element => {
      if (element.elementType === 'textbox') {
        questions.push(new TextboxQuestion(element));
      } else if (element.elementType === 'dropdown') {

        //Need to push the data that comes from service result (res.data) to the options

        questions.push(new DropdownQuestion(element));

        console.log("step 1");

      //The service which  i call in real time..

        // return this.http.get(element.optionsUrl).subscribe(res => {

        //res.data has the following array, Using foreach pushing to elements.options.

      //   [
      //   { "key": 'average', "value": 'Average' },
      //   { "key": 'good', "value": 'Good' },
      //   { "key": 'great', "value": 'Great' }
      // ],

        // res.data.forEach(result => {
          console.log("step 2");
        //   element.options.push(result);
        // });
        // console.log(element.options) give values as the above [
      //   { "key": 'average'...
        console.log("step 3");
                // console.log(element.options) give values as the above [
      //   { "key": 'average'...
        // });
        console.log("step 4");
      //But here console.log(element.options) gives empty 
      }
    });

    return questions.sort((a, b) => a.order - b.order);
  }

【问题讨论】:

  • 不确定是否理解问题,因为看起来您已经有了解决方案:服务调用是异步的,因此您将在其回调中获得结果(步骤 2 和 3)。在这里你可以成功填写array,所以你需要对array做什么,在服务回调中发起。
  • 我认为这基本上只是旧经典的另一个复制品stackoverflow.com/questions/14220321/…
  • @Zim,但是如果我用 console.log(array) 代替第 4 步,那么它给出空数组,但代替第 2 步和第 3 步,它给出值..
  • @undefined: 是的,因为到了第4步的时候,异步调用还没有结束!
  • 将结果放入 setTimeout

标签: javascript angular typescript angular-services angular4-router


【解决方案1】:

如果将函数 getQuestion 转换为 Observable 的第一步。

为什么它是必要的?因为你需要调用 this.http.get(element.optionsUrl)。这是异步的(所有 http.get 返回 observable)。并且您需要等待被调用完成才能获取数据。 observable 的好处是在“订阅函数”内部你有数据。

因此,我们必须想到“服务返回 observables,组件订阅服务”。

好吧,让问题。主要问题是我们需要多次调用 http.get。正如我们所知,所有对 http 的调用都是异步的,所以如何确保我们拥有所有数据(请记住,我们只有数据进入 subscribe 函数。因为我们不希望有多个订阅 - 最好是拥有没有订阅 - 在我们的服务中,我们需要使用 forkJoin。ForkJoin 需要一个调用数组,并返回一个结果数组。

所以首先是创建一个observable数组,然后我们返回这个observable数组。等一会!我们不想返回一个带有选项的数组,我们想要一个可观察的问题。为此,尽管返回了 observable 数组,但我们返回了一个使用这个 observable 数组的对象。我在响应的底部放了一个简单的例子

getQuestions():Observable<any[]> { //See that return an Observable

    let questions: any = [];

    //First we create an array of observables
    let observables:Observable<any[]>[]=[];
    this.jsonData.forEach(element => {
      if (element.elementType === 'dropdown') {
        observables.push(this.http.get(element.optionsUrl))
      }
    }
    //if only want return a forkjoin of observables we make
    //return forkJoin(observables)
    //But we want return an Observable of questions, so we use pipe(map)) to transform the response

    return forkJoin(observables).pipe(map(res=>
    {  //here we have and array like-yes is an array of array-
       //with so many element as "dowpdown" we have in question
       // res=[
       //      [{ "key": 'average', "value": 'Average' },...],
       //        [{ "key": 'car', "value": 'dog },...],
       // ],
       //as we have yet all the options, we can fullfit our questions
       let index=0;
       this.jsonData.forEach((element) => { //see that have two argument, the 
                                                  //element and the "index"
          if (element.elementType === 'textbox') {
             questions.push(new TextboxQuestion(element));
          } else if (element.elementType === 'dropdown') {
               //here we give value to element.options
               element.option=res[index];
               questions.push(new DropdownQuestion(element));
               index++;
          }
       })
       return question
    }))
 }

注意:如何使用“of”转换返回可观察值的函数:简单示例

import { of} from 'rxjs';

getData():any
{
   let data={property:"valor"}
   return data;
}
getObservableData():Observable<any>
{
   let data={property:"observable"}
   return of(data);
}
getHttpData():Observable<any>
{
    return this.httpClient.get("myUrl");
}
//A component can be call this functions as
let data=myService.getData();
console.log(data)
//See that the call to a getHttpData is equal than the call to getObservableData
//It is the reason becaouse we can "simulate" a httpClient.get call using "of" 
myService.getObservableData().subscribe(res=>{
     console.log(res);
}
myService.getHttpData().subscribe(res=>{
     console.log(res);
}

注意 2:forkJoin 和 map 的使用

getData()
{
    let observables:Observables[];

    observables.push(of({property:"observable"});
    observables.push(of({property:"observable2"});

    return (forkJoin(observables).pipe(map(res=>{
        //in res we have [{property:"observable"},{property:"observable2"}]
        res.forEach((x,index)=>x.newProperty=i)
        //in res we have [{property:"observable",newProperty:0},
        //                {property:"observable2",newProperty:1}]
       }))
}

更新 还有其他方法可以做这些事情。我认为最好有一个返回完整“问题”的函数。

//You have
jsonData:any=....
//So you can have a function that return an observable
jsonData:any=...
getJsonData()
{
   return of(this.jsonData)
}
//Well, what about to have a function thah return a fullFilled Data?
getFullFilledData()
{
   let observables:Observables[]=[];
   this.jsonData.forEach(element => {
      if (element.elementType === 'dropdown') {
         observables.push(this.http.get(element.optionsUrl))
      }
   })
   return forkJoin(observables).pipe(map(res=>
      let index = 0;
      this.jsonData.forEach((element) => {
      if (element.elementType === 'dropdown') {
         element.options = res[index];
         index++;
      }
   })
   return this.jsonData
   }))
}

通过这种方式,您无需更改组件。如果你调用 getFullfilledData 你有(订阅)数据

查看stackblitz

【讨论】:

  • 感谢 Eliseo,我认为您的工作太忙了。我需要在此解决方案中进行深入分析,我将彻底了解它。
  • @undefined,我更新了答案,让事情变得更“合理”。我认为最好有一个函数返回一个可观察的完整数据并且不接触组件
  • @undefined,更新了我的答案,添加了一个堆栈闪电战并更正了一些错误。在 stackblitz 中,您有一个返回完整问题的服务。请阅读 stackblitz 的“README”文件,以更好地了解我尝试做的事情。 (stackblitz 只展示了如何从文件中读取问题的部分内容)
  • Eliseo,我对我们的表单有另一个疑问。如果我需要使用不同的 JSON 数据集生成另一个表单,那么我是否需要创建一个新的 json 变量并需要创建另一个函数(如问题)service.ts?否则我们如何管理在单个函数中生成的表单的任何 JSON 数据??
【解决方案2】:

1-

好吧,一旦有了具体的用例,您就可以使用不同的方式获得相同的结果,但通常您可以尝试使用 async await: p>

async someFunction() {
    this.asyncResult = await this.httpClient.get(yourUrl).toPromise();
    console.log("step 4");
  }

不再需要订阅,一旦从“yourUrl”中获取数据,Observable 将被转换为promise 并解析promise,然后返回的数据存储在“asyncResult”变量中。此时将执行最后一个控制台here you'll find a little use case

PS: this.httpClient.get(yourUrl) 是在你的this.service.getRest(url) 中实现的


2-

或者只是将您的console.log("step 4"); 移动到 subscribe 方法范围内以确保顺序。 (Javascript 有一个著名的异步行为,请谷歌了解更多详情)

【讨论】:

  • 在我的实际应用程序中,我在该函数中有一个 forEach。在 forEach 中只有我要进行服务调用。如果我在函数之前给出异步,那么它显示 @987654329 @ 但在 foreach 之外,我没有收到等待错误。但只有在 foreach 内部,我才能获得服务的 URL。
  • @undefined 你能提供一点stackblitz吗?
  • 给我几秒钟@undefined
  • stackblitz.com/edit/angular-x4a5b6-ng8m4z 我已经解释了整个应用程序流程.. 它的工作示例..
【解决方案3】:

查看以下时间线:

无法保证在第 4 步之前返回服务,因此无法保证array 将在第 4 步中填写。 确保使用填充数组的推荐方法是在服务回调中移动数组处理逻辑,这将对应于图片上的第二个向下箭头。

【讨论】:

  • 我已经用实时工作的角度堆栈闪电更新了我的问题。请帮助我实现结果..
【解决方案4】:

您的函数正在调用异步 API 调用,因此您将无法在 .subscribe() 函数之前或之后获取数组的值。而且你需要在函数之外声明你的数组。

然后,如果你得到你的数据,你只需要调用另一个函数。

let array = [];

someFunction() {


 this.service.getRest(url).subscribe(result => { 

   result.data.forEach(element => {

    array.push(element); // Pushing all the objects comes from res.data     

   });

   this.anotherFunction();

 });

  anotherFunction()
  {
     console.log(this.array)//you can access it here 
  }

}

【讨论】:

    【解决方案5】:

    您的第 4 步超出了订阅逻辑。在第 3 步之后将其移入其中,它将作为最后一个执行。

    Observables 发送三种类型的通知:下一个、错误和完成。 https://angular.io/guide/observables 如果要处理肯定响应,则必须将每个 logik 放在下一个通知中。

    myObservable.subscribe(
     x => console.log('Observer got a next value: ' + x),
     err => console.error('Observer got an error: ' + err),
     () => console.log('Observer got a complete notification')
    );
    

    如果你有几个 observables 并且想要一个接一个地处理它们,你可能也会对 concatMap 之类的扁平化策略感兴趣。 https://medium.com/@shairez/a-super-ninja-trick-to-learn-rxjss-switchmap-mergemap-concatmap-and-exhaustmap-forever-88e178a75f1b

    【讨论】:

      猜你喜欢
      • 2017-08-03
      • 1970-01-01
      • 2019-11-25
      • 1970-01-01
      • 2019-12-03
      • 2020-06-23
      • 1970-01-01
      • 2015-07-19
      • 1970-01-01
      相关资源
      最近更新 更多