2

Scenario: The data from mutiple rest calls must be aggregated into one single object and returned as the rest response of an initial request which is served via Node.js.

Issue: The rest response is not waiting until the observable is finished thus the mutations (aggregations) are realized after the rest response has been dispatched.

//teamsController class invoked via GET /teams 

import * as Rx from 'rxjs/Rx'
import http from 'axios'
import Teams from '../models/teams'

const teamsAPI = "http://localhost:8081/api/v1/teams/players/";
const usersAPI = "http://localhost:8082/api/v1/users/";

exports.getTeamByPlayer = function (req, res) {

let username= req.get("username");

    Rx.Observable.fromPromise(fetchTeam(username))
        .map(team => {
            Rx.Observable.from(team.players).subscribe(player => {
                console.log(`Player name is ${player.username}`);
                Rx.Observable.fromPromise(fetchUser(player.username))
                    .map(avatar => avatar.avatar)
                    .subscribe(avatar => {
                        player.avatar = avatar;
                        console.log(player)
                    })
            });
            return team;
         })
        .subscribe(result => {
            console.log(`result is ${JSON.stingify(result)}`);
            res.json(result);

        })
}


/**
 * Fetch a team by a player
 *
 * @param name The name of the team
 * @returns {Promise.<Teams>}
 */
function fetchTeam(name) {
    return http.get(teamsAPI + name)
        .then(response => new Teams(response.data.data))
        .catch(error => {
            throw  new Error("todo: fill error message");
        })
}

/**
 * Fetch a user given its username
 *
 * @param username The username of the player
 * @returns {Promise.<TResult>}
 */
function fetchUser(username) {
    return new Promise(function (resolve, reject) {
        console.log(`fetching user: ${username}`);
        resolve();
    }).then(() => {
        return {
            "avatar": {
                "flagColor": "dummyValue",
                "flagCrest": "dummyValue"
            }
        }
    });

Log results:

Player name is username1
fetching user: username1
Player name is username2
fetching user: username2
result is {"id":"5a1c2a4030c39e5d88aed087","name":null,"avatar":{"flagColor":"abcdefg","flagCrest":"hijklmn"},"description":"string","motto":"string","players":[{"userId":"59b94a7b8b68ef0a048e85c1","username":"username1","status":"ACTIVE","role":"ADMIN","dateJoined":1511795264314,"dateInvited":null,"score":0},{"userId":"59b94a7b8b68ef0a048e85c1","username":"username2","status":"ACTIVE","role":"MEMBER","dateJoined":1511795264314,"dateInvited":null,"score":0}],"score":0,"type":"TEAM","open":true,"location":{"longitude":0,"latitude":0,"country":"string"},"owner":"username1","dateCreated":1511795264314}
{ userId: '59b94a7b8b68ef0a048e85c1',
  username: 'username1',
  status: 'ACTIVE',
  role: 'ADMIN',
  dateJoined: 1511795264314,
  dateInvited: null,
  score: 0,
  avatar: { flagColor: 'dummyValue', flagCrest: 'dummyValue' } }
{ userId: '59b94a7b8b68ef0a048e85c1',
  username: 'username2',
  status: 'ACTIVE',
  role: 'MEMBER',
  dateJoined: 1511795264314,
  dateInvited: null,
  score: 0,
  avatar: { flagColor: 'dummyValue', flagCrest: 'dummyValue' } }

AppServer: Node.JS v8.7.0, Middleware: expess 4.16.2 , Libs: RxJs 5.0.0-beta.12, axios 0.17.1

1

1 Answer 1

4

first, stop converting your http requests to promises. It makes things way more complicated than they need to be.

function fetchTeam(name) {
    return http.get(teamsAPI + name).map(res => new Teams(res.json().data));
}

function fetchUser(username) {
    return Rx.Observable.of({
        "avatar": {
            "flagColor": "dummyValue",
            "flagCrest": "dummyValue"
        }
    });
}

If some outside consumer NEEDS a promise based api, then make public functions that wrap these private functions in promises.

second, map is a synchronous operator, you can't execute an asynchronous operation inside of it, you need to use an asynchronous operator that does the subscription work for you. If you ever find yourself subscribing within an observable stream, you're doing something wrong.

let username= req.get("username");

fetchTeam(username)
    .switchMap(team => { // switchMap will subscribe to inner observables
        //build observables for fetching each avatar, pay close attention, I'm mixing the array map operator and the rx map operator in here
        let players$ = team.players.map(player => fetchUser(player.username).map(avatar => avatar.avatar)); 

        return Rx.Observable.forkJoin(players$); // use forkjoin to executre requests
     }, (team, playerAvatars) => { // use second switchMap argument to combine results
         team.players.forEach((player, idx) => player.avatar = playerAvatars[idx]);
         return team;
     })
    .subscribe(result => {
        console.log(`result is ${JSON.stingify(result)}`);
        res.json(result);

    });
Sign up to request clarification or add additional context in comments.

4 Comments

Thank you it works! What is the best practise which is used to update some of the nested objects of the teams object ?
Not sure what you're asking
This line of code let players$ = team.players.map(player => fetchUser(player.username).map(avatar => avatar.avatar)) will fetch the users in perfect match with the team.players position. However is if a team has 100 players it will be unefficient to make 100 calls. Thus a new webservice will take all the usernames and retun a list of avatars in random order. In that case what is the best tactic to match the nested object based on a key (e.g. username) and make the update?
well, I'd actually disagree that 1 request for 100 items is more efficient than 100 requests for 1 item since server overhead is shared between the requests, it really depends on more context which is more efficient. But regardless, you could just feed your list of usernames to a single request, then just aggregate based on some key yea, nothing stopping you at all. You just need to map the avatars into a keyed object with the structure {[username]: avatar} and then when you forEach, just look up based on that key.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.