3

I have a node project implementing express to handle incoming GET/POST requests, I have created routes to handle different kinds of requests. I have a route as /api/twitter/search which basically calls a function that uses promises to fetch twitter feeds and sends the data in response using response.write

Now the situation here is, when I make two simultaneous requests to the same route with the same input data, then somehow the running code of both the requests coincide giving inconsistent response. I want any number of requests to the server to be handled separately, even if the action to the request has a number of async calls/promises in place.

Can anyone please help me here. I am totally new to js development, so any help will be appreciated

Following is the code:

The route that I have created goes as follows:

app.post('/api/twitter/fetch',function(req,res){
        var hashTag = req.body.hashtag;
        var userVals = {
            userEmail: req.body.uemail,
            tokenVal: req.body.utoken
        }
        tokenController.validateToken(userVals,function(error,data){
            returnObject = {status:true, data:'', error:''};
            if(error!=''){
                returnObject.status = false;
                returnObject.data = data;
                returnObject.error = error;
                res.send(returnObject);       
            }else{
                var twitterController = require('../controllers/testTwitterController');
                twitterController.fetchTweets(res,hashTag);
            }
        });
    });

Following is the code for the fetchTweets function:

var Twitter = require('twitter');
var params = require('../config/params.js');
var moment = require('moment');
//var promise = require('promise');
var globalHashTag = ''; var tweetArr = []; var  responseHandle = [];
client = new Twitter({
    consumer_key: params.consumer_key,
    consumer_secret: params.consumer_secret,
    access_token_key: params.access_token_key,
    access_token_secret: params.access_token_secret

});
function fetchTweetsAsync(hashTag,max_id){
    var days = 7; //Days you want to subtract from today
    var tillDate = (new Date()).toISOString().split('T')[0];
    var sinceDate = (new Date((new Date).getTime() - ((days) * 24 * 60 * 60 * 1000))).toISOString().split('T')[0];
    var param = {q:'#'+hashTag+' since:'+sinceDate+' until:'+tillDate,include_entities:true,result_type:'recent',count:100};
    if(max_id !== '' || typeof max_id != undefined)
        param.max_id = max_id;
    return new Promise(function(resolve,reject){
        // do a thing, possibly async, then..
        client.get('search/tweets',param,function(error,tweets,response){
            if(error){
                console.log(error);
                process.exit(1);
            }
            if(tweets.statuses.length){//If we get tweets, send them via promise
                //console.log("\n\n\n\n--------------------------------------------------------- Tweets fetched for #"+hashTag+ "-------------------------------------------------------------\n\n\n");
                resolve(tweets.statuses,hashTag);
            }
        });
    });
};


function getMaxHistory(tweets,hash){
    //console.log("\n\n~~~~~~~~~~~~~~~~Total: "+ tweets.length + " results found!~~~~~~~~~~~~~~~~\n\n");
    tweets.map((tweet)=>{
        process.stdout.write('.');
        created_at = moment(tweet.created_at).format("YYYY-MM-DD");
        tweetArr.push(created_at);
    });
    max_id = tweets[tweets.length - 1].id - 1;
    if(tweets.length == 100){
        process.stdout.write('*');
        return fetchTweetsAsync(hash,max_id).then(getMaxHistory);
    }
    else{
        var total = tweetArr.length;
        var finalArr = []; 
        finalArr = getDateWiseCount(tweetArr);
        tweetArr = [];
        console.log("Count array generated!");
        console.log("Total: "+total);
        finalArr = JSON.stringify({status:true,total:total,counts:finalArr});
        return finalArr;
        // console.log(finalArr);
        //responseHandle.send([{status:true,total:total,counts:finalArr}]);
    }
}

function getDateWiseCount(tweetArr){
    tweetArr = tweetArr.sort(); //We have a sorted array, need to get the counts now


    var current = null;
    var count = 0; returnarr = [];

    for(var i = 0; i < tweetArr.length; i++)
    {
        if(tweetArr[i] != current)
      {
        if(count > 0)
        {
            var date = new Date(current).toISOString();
            var val = count;
            returnarr.push({date:date,value:val});
            //console.log(returnarr);
            //console.log(current + " " + count + "<br/>");

        }
        current = tweetArr[i];
        count = 1;
      }
      else
      {
        count++;
      }
    }

    if(count > 0){
        var date = new Date(current).toISOString();
        var val = count;
        returnarr.push({date:date,value:val});
    }
    return returnarr;
}

module.exports = {
    fetchTweets: function(res,hashTag){
        responseHandle = res;
        fetchTweetsAsync(hashTag).then(getMaxHistory).then(function(finalArr){
            res.write(finalArr);
            res.end();
        });
    }
};
Kushagra
  • 626
  • 6
  • 20
  • I think you explained it wrong, " I make two simultaneous requests to the same route with the same input data, then somehow the results of both the requests coincide" what do you even mean? giving the same results on the same get and input data defines consistency, what are you trying to say? – Dinca Adrian Jun 12 '17 at 11:28
  • Sorry I was not clear enough. The thing is I hit using postman on url: localhost:8080/api/twitter/fetch with some input data. This route is linked to a method that uses the input data to return some output using res.write. When I hit the same url twice at the same time, by opening two tabs of postman, the server is not handling it as two different requests, and rather using the request body of first request body in second instance and vice-versa – Kushagra Jun 12 '17 at 11:46
  • 3
    Sounds like you're not properly declaring variables so they get "promoted" to globals. We can, of course, only guess, because you're not posting any code. – robertklep Jun 12 '17 at 12:31
  • Now I understand, but can you post that part of the router with the get, or read this and try to apply to your code? https://stackoverflow.com/questions/19850234/node-js-variable-declaration-and-scope . – Dinca Adrian Jun 12 '17 at 13:27
  • @robertklep Thanks a lot for the answer, it worked out for me. I am sorry I couldn't post the code as I did not have access to it the time I was posting the question. The issue apparently was with variable scope declaration. If you want you can post the answer separately and I shall up-vote it for clarity. Thanks! – Kushagra Jun 13 '17 at 11:40

1 Answers1

4

Unexpected behaviour when handling concurrent requests, especially where one request seems to "influence" another request, are almost always caused by variables not being declared (using var, let or const) properly.

This may cause these variables to become globals, meaning that they get shared among all requests, which can lead to all kinds of issues.

If possible, adding linter support to your editor may help catch these situations. For Vim, a commonly used plugin is syntastic, which can use eslint to syntax-check JS code. There are equivalent plugins for almost all common editors.

robertklep
  • 198,204
  • 35
  • 394
  • 381
  • I am still facing the issue. Thankfully, I now have access to the code, I'm going to edit the question and post the answer in a moment. I am quite sure declaration might be the issue but don't know how to handle it in the given code. Please help! – Kushagra Jun 14 '17 at 08:54
  • Incorrectly declared or undeclared variables: `returnObject`, `created_at`, `max_id`, `return_arr` and `tweetArr`. – robertklep Jun 14 '17 at 09:07
  • can you please try and run the code at your end. I am not able to find a solution even by defining them locally using `var` Please note, if you set it up remove the token validation thing from the route code – Kushagra Jun 14 '17 at 09:10
  • Let me know if you need the API key, etc – Kushagra Jun 14 '17 at 09:37
  • What about `req` in express? is it treated as global variable? – Rashomon Oct 22 '18 at 11:24
  • @Rashomon no, `req` is passed in as an argument. – robertklep Oct 22 '18 at 11:24
  • @robertklep I have been always worried about this. Thanks! – Rashomon Oct 22 '18 at 11:27