Performing advanced http requests in rxjs

2020-02-14 20:07发布

问题:

I have the following objects:

class Question {
    idQuestion: string;
    question: string;
    typeQuestion: string;
}

class Answer {
    idAnswer: string;
    idQuestion: string;
    answer: string;
}

class Option {
    idOption: string;
    idQuestion: string;
    option;
}

And I want to populate the following object:

class QuestionOptionsAnswer {
    question: Question;
    answer: Answer;
    options: Option[];
}

For now, I have a service for each kind of object, so we can illustrate it in the following way:

questionService.getQuestions();
answerService.getAnswers();
optionService.getOptions();

To populate a questionoptionsanswer object I have doing nested requests:

questionService.getQuestions()
    .subscribe(
        answerService.getAnswers()
           .subscribe(
               optionService.getOptions()
                  .subscribe();
           )
    )

I can populate correctly the questionoptionsanswer object, but is is slow, so I think I am making a bad approach. The idea behind having a questionoptionsanswer is for rendering in a easy way the html with angular2 directives.

I read about flatMap, switchMap, forkJoin but I am not quite sure in how to use them.

Which could be a good approach to load this data?

questionoptionsanswer will have a question object, an answer associated with it, and its possible options depending on the typeQuestion i.e: select, radio, multiple, etc.

回答1:

So you need to call the first request and wait for its response and then call the two other requests (for options and the answer) at the same time.

Since I want to know when both responses are ready I'll use Observable.forkJoin() operator that emits a single array with all values of source Observables when they completed and then add data to the qoa variable that I'll just pass to the Observer when it subscribes.

Operators such concat() or concatMap() are good to make multiple HTTP calls in order but not very useful when you need to create multiple Observable to construct one large response you want to emit (QuestionOptionsAnswer in your case).

I also used Observable.of(...) to simulate HTTP requests. I don't know what's your usecase so you'll maybe don't use Observable.create() and use Subject instead:

function getQOA() {
    return Observable.create(observer => {

        Observable.of({ question_id: '1' }).subscribe(response => {
            var qoa = new QuestionOptionsAnswer();
            let question = new Question();
            question.idQuestion = response.question_id;

            qoa.question = question;

            let obs1 = Observable.of({ answer_id: '1', answer: 'bla' });
            let obs2 = Observable.of([{ option_id: '1', option: 'ble' }]);

            Observable.forkJoin(obs1, obs2).subscribe(responses => {
                let [answerResponse, optionsResponse] = responses;

                let answer = new Answer();
                answer.idAnswer = answerResponse.answer_id;
                answer.answer = answerResponse.answer;
                qoa.answer = answer;

                qoa.options = optionsResponse.map(o => {
                    let option = new Option();
                    option.idOption = o.option_id;
                    option.option = o.option;
                    return option;
                });

                observer.next(qoa);
            });
        });
    });
}

getQOA().subscribe(qoa => console.log(qoa));

Prints to console:

QuestionOptionsAnswer {
  question: Question { idQuestion: '1' },
  answer: Answer { idAnswer: '1', answer: 'bla' },
  options: [ Option { idOption: '1', option: 'ble' } ] }