2

I'm new to Observables; I have to convert a nested for loop with Observables and, when a condition is meet, wait for a certain amount of time.
I was able to convert the for-loop with Observables, but I don't reach to achieve the "pause" of the flow of 5 seconds after the globalCounter variable value is set to 5.
How to do it? Thanks.

import { map, Observable, Subject } from "rxjs";

const array1 = ["Alfa", "Beta", "Gamma"];
const array2 = ["Laura", "Valentina", "Cristina"];
const array3 = ["Red", "Green", "Blue"];

const mySubject = new Subject<number>();
let globalCounter = 0;

const myObservable1$ = new Observable<string>((observer1) => {
  array1.map((u) => {
    observer1.next(u);
  });
});

const pipeline1 = myObservable1$
  .pipe(
    map(async (value1) => {
      const return2 = await fObservable2(value1);
      return2.subscribe({});
    })
  )
  .subscribe();

async function fObservable2(value1: string) {
  return new Observable<string>((observer2) => {
    array2.map(async (d) => {
      const valueToPassTofObservable3 = value1 + " --- " + d;
      const return3 = await fObservable3(valueToPassTofObservable3);
      return3.subscribe({
        next: async (value3) => {
          console.log(
            `OBSERVABLE2 - value3: ${value3} - globalCounter: ${globalCounter}`
          );
        },
      });
    });
  });
}


async function fObservable3(value2: string) {
  let toBeReturn3;
  if (globalCounter === 5) {
    await sleep();
  }
  return new Observable<string>((observer3) => {
    array3.forEach((t) => {
      toBeReturn3 = t + " --- " + value2;
      observer3.next(toBeReturn3);
      mySubject.next(globalCounter++);
    });
  });


}

function sleep() {
  return new Promise((resolve) => {
    setInterval(() => {
      globalCounter = 0;
      resolve(true);
    }, 5000);
  });
}

user1
  • 556
  • 2
  • 6
  • 22

2 Answers2

0

Make the condition into an observable as well. Then you can use takeUntil to wait for that observable to complete. If your condition does not complete you can use a switchMap. This lets you swap your current observable with another one. Therefore the next step is "paused" until a new value of that observable is emitted.

Also I recommend to add a $ after the name of on observable to make it very clear.

let globalCounter$ = new BehaviousSubject(0);

// instead of counter +1 you can use the following to increment the counter
this.globalCounter$.next(this.globalCounter.value + 1);

// where you want to wait
// .pipe(filter(counter => counter < 5)) creates a new observable that only emits values when the counter >= 5
takeUntil(
  this.globalCounter$.pipe(filter(counter => counter < 5))
})
Vincent Menzel
  • 1,040
  • 1
  • 6
  • 17
0

First of all, you are mixing up observables and promises.

Observables

  • An observable is an object that you can subscribe to. Subscribing means that some callback is executed when a value is received from that observable. An observable can send multiple values, which means the callback is executed multiple times.
  • Observables can be piped. Piping means that you somehow create a new observable from one or more observables. For example, you can combine observables, wait for some other observable before subscribing to another one, etc. The possibilities are endless
  • Observables are not the same thing as promises (though you can convert it into a Promise). Therefore, the async / await syntax does not work (nor does it make sense, because an observable can emit multiple values)

How to solve your problem

Use the correct pipes. You can find them using https://rxjs.dev/operator-decision-tree.

But I will help you a little. If I understood your code correctly, you want to:

  • Loop over array1
  • For every element in array1, loop over array2
  • For every element in array2, loop over array3
  • if the function fObservable3 is executed for the 5'th time, wait for 5 seconds.

You can achieve that with

class SomeClass {
  private loopedElementsCounter = 0;
  
  constructor(){
    from(array1).pipe(
      concatMap(array1El => loopOverSecondArray(array1El))
    )
    .subscribe(console.log);
  }

  private loopOverSecondArray(array1El): Observable<any> {
    return from(array2).pipe(
      concatMap(array2El => loopOverThirdArray(array1El, array2El))
    )
  }

  private loopOverThirdArray(array1El, array2El): Observable<any> {
    return from(array3).pipe(
      concatMap(array3El =>  sleepIfNecessary(array1El, array2El, array3El))
    )
  }

  private sleepIfNecessary(array1El, array2El, array3El): Observable<any> {
    const obs = of('construct your string from array1El, array2El, array3El here')
    this.loopedElementsCounter += 1;
    if (this.loopedElementsCounter !== 5) return obs;
    return obs.pipe(delay(5000))
  }
}

Note that concatMap and delay are pipes, which can be found on the RxJS website. of and from are functions to easily construct observables, which can also be found on the RxJS website.