Как обрабатывать данные, поступающие с сервисом поздно? - PullRequest
0 голосов
/ 16 ноября 2018

В моем угловом приложении мне нужно сохранить данные в массив, который будет пустым на начальном этапе.

Пример :

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 Шаг 3

Здесь, после шага 1, шаг 4 вызывает, а затем шаг 2 .. Поэтому, если я console.log(array) вместо шага 4, он снова дает пустой массив ..

Но на местеиз step 2 and 3 это дает значение .. Выходя из службы, значение пустое.

И, следовательно, всегда я получаю пустое значение в array.

Пожалуйста, помогите мне сохранитьданные в переменную, даже несмотря на то, что существует время ожидания вызова службы и ответа.

Попытка изменить код в течение длительного времени, но не может заставить его работать ..

Редактировать :

Я дал ниже приложение реального времени, с которым я сейчас работаю stackblitz lчернила 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);
  }

Ответы [ 5 ]

0 голосов
/ 17 ноября 2018

Первый шаг, если ваша функция getQuestion преобразуется в Observable.

Почему это необходимо?Потому что вам нужно вызвать this.http.get (element.optionsUrl).Это асинхронно (все возвращаемые http.get наблюдаемые).И вам нужно дождаться окончания звонка, чтобы получить данные.Преимущество наблюдаемого в том, что внутри «функции подписки» у вас есть данные.

Поэтому мы должны думать, что «службы возвращают наблюдаемые, компонент подписывается на службы».

Ну,пусть вопрос.Основная проблема в том, что нам нужно несколько звонков на http.get.Как мы знаем, все вызовы http являются асинхронными, так как можно быть уверенным, что у нас есть все данные (помните, что у нас есть только данные в функцию подписки. Поскольку мы не хотим иметь несколько подписок, лучше всего иметьв нашем сервисе нет подписки, нам нужно использовать forkJoin. ForkJoin нужен массив вызовов и вернуть массив результатов.

Итак, сначала нужно создать массив наблюдаемых, а затем мы возвращаем этот массив наблюдаемых.Подождите минутку! Мы не хотим возвращать массив с опциями, нам нужны наблюдаемые вопросы. Для этого, несмотря на возврат массива наблюдаемых, мы возвращаем объект, который использует этот массив наблюдаемых.пример внизу ответа

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

0 голосов
/ 16 ноября 2018

Посмотрите на следующую временную шкалу: exec timeline

Нет гарантии, что возврат услуги произойдет до шага 4, поэтому на шаге 4 не будет заполнена гарантия array. Рекомендуемый способ обеспечения работы с заполненным массивом - переместить логику обработки массива в обратный вызов службы, что будет соответствовать второй стрелке вниз на рисунке.

0 голосов
/ 16 ноября 2018

1-

Ну, здесь вы можете достичь того же результата, используя разные способы, когда есть конкретный вариант использования, однако в целом вы можете попробовать использовать async await:

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

Вам больше не нужно подписываться, как только данные извлекаются из «yourUrl», Наблюдаемые будут преобразованы в обещание, и обещание будет выполнено , то возвращенные данные сохраняются в переменной «asyncResult».В этот момент будет выполнена последняя консоль , , здесь вы найдете небольшой пример использования .

PS: this.httpClient.get(yourUrl) - это то, что реализовано в вашем this.service.getRest(url)


2-

Или просто переместите console.log("step 4"); внутрь subscribe Область применения метода для обеспечения заказа. (Javascript обладает известным асинхронным поведением, для более подробной информации обратитесь к Google)

0 голосов
/ 16 ноября 2018

Ваша функция вызывает асинхронный вызов 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 
  }

}

0 голосов
/ 16 ноября 2018

Ваш Шаг 4 находится вне логики подписки.Переместите его внутрь него после шага 3, и он будет выполнен как последний.

Наблюдаемые отправляют три типа уведомлений: следующее, ошибка и завершено.https://angular.io/guide/observables Если вы хотите обработать положительный ответ, каждый логик должен быть помещен внутри следующего уведомления.

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

Сглаживание Стратегии, такие как concatMap, также могут вас заинтересовать, если вы получите несколькоНаблюдаемые и хотят обрабатывать их один за другим.https://medium.com/@shairez/a-super-ninja-trick-to-learn-rxjss-switchmap-mergemap-concatmap-and-exhaustmap-forever-88e178a75f1b

...