Убедиться, что все наблюдаемые в цикле for завершены перед выполнением другого кода

#angular #rxjs

#angular #rxjs

Вопрос:

У меня есть фрагмент кода, который выглядит следующим образом:

 getPersons().subscribe(
    persons => {
        for (const person of persons) {
            getAddress(person.id).subscribe(
                address => {
                    person.address = address;
                }
            );
        }
        doSomethingWithAddresses();
     }
);
  

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

Ответ №1:

Вы должны использовать forkJoin RxJS, чтобы дождаться завершения цикла for..of, прежде чем возвращать все наблюдаемые.

Вот изменения, которые вы должны внести в свой код:

 getPersons().subscribe(
  persons => {
    const observablesList = [];
    for (const person of persons) {
      const getAddressObservable = getAddress(person.id);
      observablesList.push(getAddressObservable)
    }
    forkJoin(observablesList).subscribe(response => {
      // console.log(response) to check that there is a list of returned observables
      const result = persons.map((person, index) => {
        person['address'] = response[index]['address'];
        return person;
      })
      doSomethingWithAddresses();
    })
  }
);
  

В качестве альтернативы, вы можете попробовать это, чтобы предотвратить цепочку subscribe()

 getPersons().pipe(
  mergeMap(persons => {
    const observablesList = [];
    for (const person of persons) {
      const getAddressObservable = getAddress(person.id);
      observablesList.push(getAddressObservable)
    }
    return observablesList;
  })
).subscribe(response => {
  // console.log(response) to check that there is a list of returned observables
  const result = persons.map((person, index) => {
    person['address'] = response[index]['address'];
    return person;
  })
  doSomethingWithAddresses();
})
  

Комментарии:

1. Спасибо, это выглядит как правильный подход, но мне все равно нужно назначить каждый адрес каждому пользователю перед вызовом doSomethingWithAddresses() . Возможно ли это с помощью forkJoin?

2. @Spacejockey На самом деле, да. Когда вы подписываетесь на наблюдаемые из forkJoin, вы поймете, что он возвращает список возвращаемых значений из наблюдаемых. В псевдокоде вы получите что-то вроде этого [addressObj1, addressObj2, addressObj3, etc, etc] . Оттуда вы, вероятно, можете использовать Array.map() для сопоставления каждого элемента в массиве, чтобы назначить адрес каждому пользователю! Позвольте мне попытаться добавить это к моему ответу, чтобы он был более четко проиллюстрирован.

3. Дайте мне знать, если это сработает, я добавлю еще одно альтернативное решение, чтобы сделать это более чистым, поскольку я понимаю, что я здесь размещаю подписки.

4. @wentjun о, ты меня неправильно понял, приятель. на самом деле я хотел учиться. В каком-то месте я видел, что некоторые используют mergemap для подобных сценариев. но здесь вы создаете список наблюдаемых. Я попытался понять решение и задал этот вопрос

5. @SadidKhan Извините за это, я не хотел показаться резким. TBH, я думаю, есть несколько способов подойти к этому, но моя идея состоит в том, чтобы обрабатывать это «по частям» и разделять проблемы. Я подумал, что было бы неплохо, если бы мы дождались завершения наблюдаемых в цикле for..of, прежде чем переходить к следующему набору операций. Поэтому я решаю объединить их и вернуть наблюдаемые, когда циклы for..of будут полностью выполнены ..! Это своего рода «надежный» способ гарантировать, что doSomethingWithAddresses() он будет выполняться только после завершения циклов for.

Ответ №2:

 let loadedPerson;
getPersons().pipe(
  mergeMap(persons => {
  return of(persons);
}),
mergeMap(person => {
  loadedPerson = person;
  return getAddresses(person.id);
}),
map((address) => {
  loadedPerson.address = address;
}),
tap(()=>{
  doSomethingWithAddresses();
})
).subscribe();
  

Ответ №3:

Попробуйте этот подход

 methodOne() {
getPersons().subscribe(
    persons => {
        for (const person of persons) {
            getAddress(person.id).subscribe(
                address => {
                    person.address = address;
                }
            );
        }
     }
);
}

async methodTwo() {
await methodOne();
doSomethingWithAddresses();
}
  

Комментарии:

1. Поскольку methodOne() isn не отслеживает свои дочерние подписки или ничего не возвращает, не doSomethingWithAddresses() будет ли запускаться, не дожидаясь их завершения?