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.

Community
  • 1
  • 1
akalanka
  • 553
  • 7
  • 21
  • 1
    In `searchPlayer()`, `next()` is called before you get the search result. – Aynolor Oct 27 '16 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](https://github.com/caolan/async) library? For this use case its `waterfall` method is the obvious fit. – Talha Awan Oct 27 '16 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. – akalanka Oct 27 '16 at 21:20

1 Answers1

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);
Val
  • 207,596
  • 13
  • 358
  • 360
  • 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. – akalanka Oct 31 '16 at 23:31
  • 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 – Val Nov 01 '16 at 04:26
  • 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. – akalanka Nov 02 '16 at 16:57
  • The problem is when you have more than one team and `req.teams.forEach()` will call next() several times, which is not good. – Val Nov 02 '16 at 16:59
  • 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. – akalanka Nov 04 '16 at 04:26
  • I've updated my answer, which now uses `async.each`instead of looping through the teams. That should work. – Val Nov 04 '16 at 05:04
  • I made a small change since searchTeam need a parameter to search. Son in instead of searchTeam in waterfall array it was modified to `async.apply(searchTeam, req.body)` and searchTeam was changed accordigly. Still my localhost is running I debugging to find the issue – akalanka Nov 04 '16 at 18:29
  • Working now.. small glitch in the searchTeam. Thanks for the help..I'll point out this answer the in the other question. – akalanka Nov 04 '16 at 18:36