1

I'm attempting to work through a database of "events" I have and pull photos from the Instagram API based on each event's location, radius, start time, and end time. I've set up the following code on my Node server, but it is not behaving as I would expect.

The first thing I see when I run this code is sending request to Instagram for [name] with min_timestamp: [timestamp] printed for every event. I did not expect this. I would have expected to see this line logged for the first event, then updated with a new timestamp over and over until that event reaches its end time. Then event 2, iterate through timestamps, and so forth.

I end up with the same block of photos repeated over and over for each event. It's as if my code is firing off one request to Instagram (with the initial timestamp) over and over and then stopping.

Notes on my timestamp variable: For each event I set my minTimestamp variable to initially equal event.start from my database. This is used within the request sent to Instagram. Instagram returns up to 20 photos to me. Each photo has a created_time variable. I grab the most recent created_time variable and set my minTimestamp variable to equal it (minTimestamp = images[0].created_time;) for my next request sent to Instagram (to grab the next 20 photos). This continues until minTimestamp is no longer less than endTimestamp (event.end for that event from my db).

server.js code:

// modules =================================================
var express        = require('express.io');
var app            = express();
var port           = process.env.PORT || 6060;
var io             = require('socket.io').listen(app.listen(port));
var request        = require('request');
var Instagram      = require('instagram-node-lib');
var mongoose       = require('mongoose');
var async          = require('async');
var bodyParser     = require('body-parser');
var methodOverride = require('method-override');
var db             = require('./config/db');
var Event          = require('./app/models/event');

// configuration ===========================================
mongoose.connect(db.url); // connect to our mongoDB database

// get all data/stuff of the body (POST) parameters
app.use(bodyParser.json()); // parse application/json 
app.use(bodyParser.json({ type: 'application/vnd.api+json' })); // parse     application/vnd.api+json as json
app.use(bodyParser.urlencoded({ extended: true })); // parse application/x-www-form- urlencoded

app.use(methodOverride('X-HTTP-Method-Override')); // override with the X-HTTP-Method- Override header in the request. simulate DELETE/PUT
app.use(express.static(__dirname + '/public')); // set the static files location  /public/img will be /img for users

var baseUrl = 'https://api.instagram.com/v1/media/search?lat=';
var clientId = CLIENT-ID;

Event.find({}, function(err, events) {

    async.eachSeries(events, function(event, callback) {

      var name = event.event;
      var latitude = event.latitude;
      var longitude = event.longitude;
      var distance = event.radius;
      var minTimestamp = Math.floor(new Date(event.start).getTime()/1000);
      var endTimestamp = Math.floor(new Date(event.end).getTime()/1000);

      async.whilst(
        function () { return minTimestamp < Math.floor(Date.now() / 1000) && minTimestamp < endTimestamp; },
        function(callback) {
          console.log('sending request to Instagram for ' + name + ' with min_timestamp: ' + minTimestamp);
          request(baseUrl + latitude + '&lng=' + longitude + '&distance=' + distance + '&min_timestamp=' + minTimestamp + '&client_id=' + clientId,
            function (error, response, body) {
              if (error) { 
                console.log('error');
                return;
              }

                //JSON object with all the info about the image
                var imageJson = JSON.parse(body);
                var images = imageJson.data;
                var numImages = images.length;
                console.log(numImages + ' images returned with starting time ' + images[(numImages - 1)].created_time + ' and ending time ' + images[0].created_time);

                async.eachSeries(images, function(image, callback) {

                  //Save the new object to DB
                  Event.findOneAndUpdate( { $and: [{latitude: latitude}, {radius: distance}] }, { $push: {'photos':
                      { img: image.images.standard_resolution.url,
                        link: image.link,
                        username: image.user.username,
                        profile: image.user.profile_picture,
                        text: image.caption ? image.caption.text : '',
                        longitude: image.location.longitude,
                        latitude: image.location.latitude
                      }}},
                      { safe: true, upsert: false },
                      function(err, model) {
                          console.log(err);
                      }
                  );
                  console.log(numImages + ' images saved to db');
                  callback();
              }, function(err){
                  // if any of the file processing produced an error, err would equal that error
                  if( err ) {
                    // One of the iterations produced an error.
                    // All processing will now stop.
                    console.log('Images failed to process');
                  } else {
                    console.log('All images have been processed successfully');
                  }
              });

              minTimestamp = images[0].created_time;
              console.log('min_timestamp incremented to: ' + minTimestamp);
            }
          );
        },
        function (err) {

        }
      );
      callback();
    }, function(err){
        // if any of the file processing produced an error, err would equal that error
        if( err ) {
          // One of the iterations produced an error.
          // All processing will now stop.
          console.log('An event failed to process');
        } else {
          console.log('All events have been processed successfully');
        }
    });
});

// routes ==================================================
require('./app/routes')(app); // configure our routes

// start app ===============================================
console.log('Magic happens on port ' + port);           // shoutout to the user
exports = module.exports = app;                         // expose app
6
  • Sorry for the silence, I needed some sleep. Are you still grappling with this? If so, I'd like to take another look at it, because it's an interesting problem, maybe we can defeat this. Could you edit your question, adding an explanation in detail of how the min_timestamp business works? I can't work out why the photos augment it and why it eventually is equal to end_timestamp. Commented Jan 10, 2015 at 16:03
  • And what do you mean by the 'same block of photos' - just photos from the first event repeated or are the photos ALL the same? Commented Jan 10, 2015 at 16:09
  • I'm not getting anywhere really, not being able to test it. Too many questions to ask you. BUT there is one thing a bit odd: which is that there is no callback() to the main function in the whilst() bit, I'd assume that was necessary. Commented Jan 10, 2015 at 17:38
  • Thanks for your interest and effort in this. I'll edit my question to add some detail about the timestamp variables. Also, the site I'm attempting to build out is mobseen.it. If you click on one of the events you'll see what I mean by photos repeating. Each location seems to get the first set of correct photos, but then they are added over and over. Commented Jan 10, 2015 at 23:56
  • Ok, so this presumably means that min_timestamp is never getting updated, right? So my next question is: if Instagram is sending back 20 photos, why do you augment the min_timestamp using the FIRST photo? (minTimestamp = images[0].created_time;) If they are in time order, shouldn't you be using minTimestamp = images[(numImages - 1)].created_time; ? Or are they in reverse time order? Commented Jan 11, 2015 at 0:52

2 Answers 2

1

The answer is that you are missing the callback() to the whilst bit.

Here's a bit of code to illustrate:

var async = require('async');

var minTimestamp = 1;
var endTimestamp = 10;

async.whilst(
    function () { minTimestamp < endTimestamp; },
    function(callback) {
      console.log('sending request to Instagram for name with min_timestamp: ' + minTimestamp);

      minTimestamp = minTimestamp + 1;
      console.log('min_timestamp incremented to: ' + minTimestamp);
      callback();
    },
    function (err) {
        if(err){
                throw err;
        }
    }
  );

If I run this without the callback(), I get the following output:

sending request to Instagram for name with min_timestamp: 1
min_timestamp incremented to: 2

If I put back the callback() I get this:

sending request to Instagram for name with min_timestamp: 1
min_timestamp incremented to: 2
sending request to Instagram for name with min_timestamp: 2
min_timestamp incremented to: 3
sending request to Instagram for name with min_timestamp: 3
min_timestamp incremented to: 4
sending request to Instagram for name with min_timestamp: 4
min_timestamp incremented to: 5
sending request to Instagram for name with min_timestamp: 5
min_timestamp incremented to: 6
sending request to Instagram for name with min_timestamp: 6
min_timestamp incremented to: 7
sending request to Instagram for name with min_timestamp: 7
min_timestamp incremented to: 8
sending request to Instagram for name with min_timestamp: 8
min_timestamp incremented to: 9
sending request to Instagram for name with min_timestamp: 9
min_timestamp incremented to: 10

Therefore put a callback() here:

      minTimestamp = images[0].created_time;
      console.log('min_timestamp incremented to: ' + minTimestamp);
      callback(); //missing callback
    }
 );
 },
Sign up to request clarification or add additional context in comments.

5 Comments

Thank you very much. That looks like it should solve this. I'm on the road now, but will implement this as soon as I get a chance and let you know how it goes. This seems like the classic "callback hell" situation that I'd read about Node in the past. It does get pretty difficult to keep track of them all after a while.
Yes, please let me know, let's hope that's it. I've upvoted your question, it has been interesting looking through it. I've removed my comment here which referred to my wrong answer, maybe you should remove yours about the unexpected identifier as it might just confuse people reading this.
P.S. Love the mobseen site! Great idea and nice implementation.
Unfortunately this solution didn't work. When I increment minTimestamp with a hard-coded value (like your example above), minTimestamp += 1000 for example, it works perfectly. As soon as I change this to minTimestamp = images[0].created_time it breaks. By 'breaks' I mean the request is sent once for each event, the timestamp is incremented, but no further requests are sent. Strange, I'm trying to wrap my head around what may cause this.
Well, as I mentioned in a comment above, maybe the images are in reverse order? - Latest first? If so you're immediately setting minTimestamp to the latest time, so everything stops. That's all that I can think of.
0
// modules =================================================
var express        = require('express.io');
var app            = express();
var port           = process.env.PORT || 6060;
var io             = require('socket.io').listen(app.listen(port));
var request        = require('request');
var Instagram      = require('instagram-node-lib');
var mongoose       = require('mongoose');
var async          = require('async');
var bodyParser     = require('body-parser');
var methodOverride = require('method-override');
var db             = require('./config/db');
var Event          = require('./app/models/event');

// configuration ===========================================
mongoose.connect(db.url); // connect to our mongoDB database

// get all data/stuff of the body (POST) parameters
app.use(bodyParser.json()); // parse application/json 
app.use(bodyParser.json({ type: 'application/vnd.api+json' })); // parse        application/vnd.api+json as json
app.use(bodyParser.urlencoded({ extended: true })); // parse application/x-www-form- urlencoded

app.use(methodOverride('X-HTTP-Method-Override')); // override with the X-HTTP-Method- Override  header in the request. simulate DELETE/PUT
app.use(express.static(__dirname + '/public')); // set the static files location  /public/img  will be /img for users

var baseUrl = 'https://api.instagram.com/v1/media/search?lat=';
var clientId = CLIENT-ID;

Event.find({}, function(err, events) {

  async.eachSeries(events, function(event, seriesCallback) {

    var name = event.event;
    var latitude = event.latitude;
    var longitude = event.longitude;
    var distance = event.radius;
    var minTimestamp = Math.floor(new Date(event.start).getTime()/1000);
    var endTimestamp = Math.floor(new Date(event.end).getTime()/1000);

    async.whilst(
      function () { return minTimestamp < Math.floor(Date.now() / 1000) && minTimestamp <  endTimestamp; },
        function(requestFinishedCallback) {
          console.log('sending request to Instagram for ' + name + ' with min_timestamp: ' + minTimestamp);
          request(baseUrl + latitude + '&lng=' + longitude + '&distance=' + distance + '&min_timestamp=' + minTimestamp + '&client_id=' + clientId,
            function (error, response, body) {
              if (error) { 
                console.log('error');
                return;
              }

              //JSON object with all the info about the image
              var imageJson = JSON.parse(body);
              var images = imageJson.data;
              var numImages = images.length;
              console.log(numImages + ' images returned with starting time ' + images[(numImages - 1)].created_time + ' and ending time ' + images[0].created_time);

              async.eachSeries(images, function(image, imageFinishedCallback) {

                //Save the new object to DB
                Event.findOneAndUpdate( { $and: [{latitude: latitude}, {radius: distance}] }, { $push: {'photos':
                  { img: image.images.standard_resolution.url,
                    link: image.link,
                    username: image.user.username,
                    profile: image.user.profile_picture,
                    text: image.caption ? image.caption.text : '',
                    longitude: image.location.longitude,
                    latitude: image.location.latitude
                  }}},
                  { safe: true, upsert: false },
                  function(err, model) {
                    console.log(err);
                    console.log('Image processed');  
                    imageFinishedCallback();
                  }
                );

              }, function(err){
                   // if any of the image processing produced an error, err would equal that error
                   if( err ) {
                     // One of the iterations produced an error.
                     // All processing will now stop.
                     console.log('Images failed to process');
                   } else {
                     minTimestamp = images[0].created_time;
                     console.log(numImages + ' images have been processed successfully and min_timestamp has been incremented to: ' + minTimestamp);
                     requestFinishedCallback();
               }
                 });
                }
              );
           }, function(err){
                   // if any of the image processing produced an error, err would equal that error
                   if( err ) {
                     // One of the iterations produced an error.
                     // All processing will now stop.
                     console.log('Event failed to process');
                   } else {
                        console.log(name + ' has been fully processed successfully with final min_timestamp of: ' + minTimestamp);
                   }
                   seriesCallback();
                });
             }, function(err){
                    // if any of the image processing produced an error, err would equal that error
                    if( err ) {
                      // One of the iterations produced an error.
                      // All processing will now stop.
                      console.log('Something failed to process');
                    } else {
                         console.log('All events have been processed successfully');
                    }
                 });
}); 

// routes ==================================================
require('./app/routes')(app); // configure our routes

// start app ===============================================
console.log('Magic happens on port ' + port);           // shoutout to the user
exports = module.exports = app; 

5 Comments

Thankyou for posting, I'm going to take a good look tomorrow.
@mwarren, figured out the final remaining issue. The incrementation, minTimestamp = images[0].created_time; needs to occur right before the callback(). However, images[0].created_time is no longer available a this point because images only exists within the data returned from Instagram. Need to find a way to grab this timestamp and pass it to minTimestamp for updating just before the callback.
I'm going to update your answer because otherwise it's too complicated to show: we've forgotten that all the async functions have to finish before the callbacks are called. I've renamed and moved all the callbacks to show how I think they should be.
I ran an abbreviated test last night based on your help, and it ran perfectly. I'll be re-running for all 'events' tonight and crediting your answer below. Thanks again, asynchronous concepts and where to place callbacks has been a stumbling block for me, but I feel like I have a much stronger handle on it now.
Great that it's working!! We've been real a couple of noobs, looking at the code as if it runs synchronously. It's been great working on an interesting piece of code, thanks for sharing.

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.