0

I am running Express.js/Node.js application with ElasticSearch. I am trying to view results from multiple types in the same index. What I do here is run a search query and based the results of the query second search query executes. I can see that elasticsearch return results for players also by observing the node.js console. But they are not inserted to my results object/array. I am using express middleware since I have to execute two search and display results as one.

routes/index.js

function searchTeam(req, res, next){
  searchModuleTeams.searchTeams(req.body, function(data) {
    req.teams = data;
    next();
  });
}

function searchPlayer(req, res, next){
  //req.players = [];
  req.teams.forEach(function(team){
    req.body = {searchTerm:team._source.shortName};
    searchModulePlayers.searchPlayers(req.body, function(data){
      req.players.push(data);
      console.log(req.players);
    });
  });
  next();
}

function renderResults(req, res){
  res.render('index',{
    title:'Search Teams and Players',
    teams:req.teams,
    players:req.players
  });
}

router.post('/search-tp',searchTeam, searchPlayer, renderResults);

I came up with this solution by reading post1 and post2. I can display the teams array. But nothing comes from the players array. What am I doing incorrect in here.

3
  • 1
    In searchPlayer(), next() is called before you get the search result. Commented Oct 27, 2016 at 19:01
  • as @Aynolor said your next is placed wrongly. Apart from that, I don't think using multiple middlewares just to collect data is a good approach. Have you tried aycnc library? For this use case its waterfall method is the obvious fit. Commented Oct 27, 2016 at 19:35
  • @Aynolor ,@Talha: Where should I place next(). I really want to finish invoking the searchPlayer before rendering the results. Meantime I'll look for async. Commented Oct 27, 2016 at 21:20

1 Answer 1

1

In your searchPlayer function, the next() call should be placed inside the callback called by searchPlayers(), basically exactly what you did for the searchTeam() function.

function searchTeam(req, res, next){
  searchModuleTeams.searchTeams(req.body, function(data) {
    req.teams = data;
    next();
  });
}

function searchPlayer(req, res, next){
  req.players = [];                      <--- uncomment this...
  req.teams.forEach(function(team){
    req.body = {searchTerm:team._source.shortName};
    searchModulePlayers.searchPlayers(req.body, function(data){
      req.players.push(data);            <--- ...otherwise this will fail
      next();                            <--- move next() here
    });
  });
}

function renderResults(req, res){
  res.render('index',{
    title:'Search Teams and Players',
    teams:req.teams,
    players:req.players
  });
}

router.post('/search-tp',searchTeam, searchPlayer, renderResults);

And as suggested by Talha Awan, you should preferably not do this in middleware but using a dedicated library, like the async one (but there are tons of others)

import waterfall from 'async/waterfall';

function searchTeam(callback){
  searchModuleTeams.searchTeams(req.body, function(teams) {
    callback(null, teams);
  });
}

function searchPlayer(teams, callback){
  let teamPlayers = [];
  async.each(teams, function(team, teamCallback) {
    let search = {searchTerm: team._source.shortName};
    searchModulePlayers.searchPlayers(search, function(players){
       teamPlayers.push(players);
       teamCallback();
    });
  }, function(err) {
    callback(err, teams, teamPlayers);
  });
}

function renderResults(req, res){
  async.waterfall([
    searchTeam,
    searchPlayer
  ], function (err, teams, players) {
    res.render('index',{
      title:'Search Teams and Players',
      teams: teams,
      players: players
    });
  });
}

router.post('/search-tp', renderResults);
Sign up to request clarification or add additional context in comments.

8 Comments

Thanks for the very explanatory answer. I have tried both of them. Currently waterfall library doesn't work since I couldn't figure out a way to pass the search parameter to the searchTeam. Anyway considering the first option by placing next() inside the loop, I have trouble in accessing the players array in renderResults. players supposed to be an array of arrays where each array contains player objects. When only one team is available teams array contains one item and players array contain set of players of the team. When more than one team is returned they need to be arranged.
It is definitely possible to bind the function you give to async with a given context (i.e. this + params). For instance, async.waterfall([ searchTeam.bind(null, 42), searchPlayer ] if you want to run the searches for team id=42
I think I should try async.waterfall. With the current structure I get 'Can't set header after they are sent' when I have more than one players object push to the req.players. The log shows the data. But since next() is invoked I think it is already sent to renderResults and no way to rendering the results with newer data in later iterations of the loop. Is there any other way to iterate a function properly and get all the results before rendering them. I think I am stuck in here due to asynchronous nature of js.
The problem is when you have more than one team and req.teams.forEach() will call next() several times, which is not good.
I figured that. Then I turned to research on promise, express-promise-router. Still no luck. My major issue concern is handling req.teams when it has many items. I found one with iterative over an array items with items.map(fn) kind of a code. But still working on it.
|

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.