rxjs equivalent of stopping an async function using 'return';

Viewed 402

I want to implement something similar below(they are easy if using promises)

async doSomething(sID){
 let student = await service.getStudent(sID);
 let teacher = await service.getTeacher(student.TeacherID);
 if(!teacher.Active){
   return;
 }

 await service.teacherSomething(teacher);
 await service.studentSomething(student);
}

I have no clue on how to do this if I am using observables instead of promises but this is the one I tried so far

doSomething(sID){
  let student;
  let teacher;
  service.getStudent(sID).pipe(
      switchMap(studentR=>{
        student = studentR;
        return service.getTeacher(student.TeacherID);
      }),
      switchMap(teacherR=>{
        teacher = teacherR;
        if(!teacher.Active){
            return of(null);
        }else{
            return service.teacherSomething(teacher);
        }
      }),
      swicthMap(teacherSomethingResponse=>{
          if(teacherSomethingResponse==null){
              return of(null);
          }else{
            return service.studentSomething(student);
          }
      })
  }).subscribe();

}

as you can see, my rxjs version seems TOO LONG compared to the promise version and I feel like i am not doing it the right way.

3 Answers

Here is how you can convert your current code to Rx style. takeWhile will complete your observable if condition not met

function doSomething(sID) {
  return from(service.getStudent(sID)).pipe(
    switchMap(student =>
      service.getTeacher(student.TeacherID).pipe(
        takeWhile(teacher => teacher.Active),
        switchMap(teacher => service.teacherSomething(teacher).pipe(takeWhile(res => res))),
        switchMap(() => service.studentSomething(student))
      )
    ))
}

async/await was developed as mainly a readability feature, so it is natural to be quite succinct visually. Using old-style Promise syntax, you would get a much longer function. So, in short - you are using observables fine, and it is longer due to expected syntax differences.

In your case, you can avoid saving the values of the teacher and student, since propagating them on the pipeline I think is pretty correct in your use case.

For that, after requesting the teacher, I would map the response and return both data student and teacher as a Tuple.

If the teacher is not active, throwing an error could be an elegant solution if you want to do something on error, if not you could also return EMPTY, that is an observable that does not emit and simply completes.

So, This is my solution, considering that the requests 'teacherSomething' and 'studentSomething' can be done in parallel since does not seem to depend on to each other

doSomething(sID){
    service.getStudent(sID).pipe(
        switchMap(studentR =>
          service.getTeacher(student.TeacherID).pipe(map((teacherR) => [studentR, teacherR]))),
        switchMap(([studentR, teacherR]) => {
          if(!teacherR.Active){
              throw new Error('Teacher is not active'); // or return EMPTY
          }
          // I think this two request may have been done in parallel, if so, this is correct.
          return zip(service.teacherSomething(teacher), service.studentSomething(student));
        })
    ).subscribe(
      ([teacherSomethingR, studentSomethingR]) => {/* do something with responses */},
      (error) => { /* Do something if teacher not active, or some request has been error...*/ }
    );
  }

If the requests can't be done in parallel, I would do just the same as done before (switchMap) and return the tuple of responses in order to do something if needed. If no needed, you can avoid that last step:

doSomething(sID){
  service.getStudent(sID).pipe(
      switchMap(studentR =>
        service.getTeacher(student.TeacherID).pipe(map((teacherR) => [studentR, teacherR]))),
      switchMap(([studentR, teacherR]) => {
        if(!teacherR.Active){
            throw new Error('Teacher is not active'); // or return EMPTY
        }
        // Both request teacher something and student something done in 'serie'
        return service.teacherSomething(teacher)
            .pipe(switchMap((teacherSomethingR) => 
                service.studentSomething(student)
                .pipe(map((studentSomethingR) => [teacherSomethingR, studentSomethingR]))
            ))
      })
  ).subscribe(
    ([teacherSomethingR, studentSomethingR]) => {/* do something with responses */},
    (error) => { /* Do something if teacher not active, or some request has been error...*/ }
  );
}

Hope this helps!

Related