0

I am trying to use Twitter REST API GET followers/ids and save it to Mongo using Mongo collection insert method

Code inside /server/server.js:

Meteor.startup(function () {
    // code to run on server at startup

    TwitterFollowersIDsCollecions = new Mongo.Collection("twitterFollowersIDs");

    var Twit = Meteor.npmRequire('twit');
    var T = new Twit({
        consumer_key:         '###',
        consumer_secret:      '###',
        access_token:         '###',
        access_token_secret:  '###'
    });

    var getTwitterFollowersIDsAsync = function (screenname, cb) {
        T.get('followers/ids', { screen_name: screenname }, function (err, data, response) {
                console.log(data);
                var vids = data.ids;
                for(var i in vids) {
                    TwitterFollowersIDsCollecions.insert({
                        twitterFollowerID:vids[i]
                    });
                }
            }
        );
    };

    Meteor.methods({
        getTwitterFollowersIDsCollectionsClient : function (screenname){
            var getTwitterFollowersIDsNow = Meteor.wrapAsync(getTwitterFollowersIDsAsync);
            var result = getTwitterFollowersIDsNow('meteorjs');
            console.log(result);
            return result;
        }
    });

});

Error in server console:

Error: Meteor code must always run within a Fiber. 
Try wrapping callbacks that you pass to non-Meteor libraries with Meteor.bindEnvironment.

Objective is to save twitter followers to a Mongo collection.

Meteor v. 1.1.0.2

Meteor packages:

  • meteor-platform
  • autopublish
  • insecure
  • differential:vulcanize
  • accounts-twitter
  • accounts-ui
  • meteorhacks:npm
  • npm-container

npm modules being used inside Meteor through meteorhacks:npm: "twit": "1.1.20" (added inside packages.json)

**UPDATE Second attempt **

Meteor.startup(function () {

  // code to run on server at startup

  TwitterFollowersIDsCollecions = new Mongo.Collection("twitterFollowersIDs");

  var Twit = Meteor.npmRequire('twit');

  var T = new Twit({
        consumer_key:         '###',
        consumer_secret:      '###',
        access_token:         '###',
        access_token_secret:  '###'
    });

  Meteor.methods({

    // this is the server method called from the client

    getTwitterFollowersIDsCollectionsClient : function (){
      setTimeout(function(){
        Meteor.call('getTwitterFollowersIDsNow', 'meteorjs');
      },10);
      return;
    },

    getTwitterFollowersIDsNow : function (screenname) {
      T.get('followers/ids', { screen_name: screenname }, function (err, data, response) {
        console.log(data);
      });
    }

  });

});

I'm then calling the below code from browser console:

Meteor.call('getTwitterFollowersIDsCollectionsClient');

The server crashes with the same error:

Error: Meteor code must always run within a Fiber. Try wrapping callbacks     that you pass to non-Meteor libraries with Meteor.bindEnvironment.

UDPATE:

getTwitterFollowersIDsCollectionsClient : function (screenname){
  Meteor.setTimeout(function(screenname){
    T.get('followers/ids', { screen_name: screenname }, Meteor.bindEnvironment(function (err, data, response) {
    console.log("from getTwitterFollowersIDsCollectionsClient : "+data.ids);

    var vids = data.ids;
    for(var i in vids)
      {
        TwitterFollowersIDsCollecions.insert({
          twitterFollowerID:vids[i]
        });
      }

    return data;
  }));

  },10);
  return;
}

Added Meteor.bindEnvironment to T.get callback method. This code worked and I was able to save the follower IDs to a mongo collection

roray
  • 756
  • 1
  • 16
  • 35
  • I have seen other SO posts like the one @ http://stackoverflow.com/questions/17235052/warning-error-meteor-code-must-always-run-within-a-fiber-when-call-method-on-se which suggests to use Meteor.bindEnvrionment. However, I could not find any docs on http://docs.meteor.com/ about Meteor.bindEnvrionment for Meteor v. 1.1.0.2. Is it a deprecated method or its out of Meteor context and covered elsewhere like in Node? – roray May 03 '15 at 21:25

3 Answers3

2

Glad you got it working, but I played around with this and Meteor provides another, super easy way: wrapAsync. At least, it was easy once I figured it out! Here's the server code I wound up with -

var T = new TwitMaker({
    consumer_key:         '...'
  , consumer_secret:      '...'
  , access_token:         '...'
  , access_token_secret:  '...'
})

var wrapGet = Meteor.wrapAsync(T.get, T);

Meteor.methods({

  getTwitImg: function(target) {
    data = wrapGet('users/show', {screen_name: target});
    if (data) {
      img_url = data['profile_image_url'];
      US.update({twitter: target}, {$set: {'targetImg': img_url}});
      return img_url;
    }
  }

});

For the client and template code see this gist: https://gist.github.com/DanAncona/a09ce375e48bfa8efeca

Dan Ancona
  • 162
  • 7
  • I came back to this question after almost a year with a similar requirement. This answer is the recommended way now http://guide.meteor.com/using-npm-packages.html#wrap-async – roray May 31 '16 at 16:39
  • still helpful after more than a year – roray Dec 12 '16 at 12:05
0

Your code is a bit confusing. It seems like you're trying to execute a web service call async, but still return the result immediately (which won't work).

First of all, you probably wouldn't need to wrap the function to fetch the followers in an async block.

If you want your server method to return something immediately to the client after it has been called, I'd use a Meteor.setTimeout (see What's the point of Meteor.setTimeout() vs just setTimeout()?) block and call another method to do the fetching:

Meteor.methods({

    // this is the server method called from the client
    getTwitterFollowersIDsCollectionsClient : function (screenname){
        Meteor.setTimeout(function() { 
          Meteor.call('getTwitterFollowersIDsNow', 'meteorjs');
        }, 10);
        return;
    },

    getTwitterFollowersIDsNow : function (screenname) {
        T.get('followers/ids', { screen_name: screenname }, function (err, data, response) {
            console.log(data);
            var vids = data.ids;
            for(var i in vids) {
                TwitterFollowersIDsCollecions.insert({
                    twitterFollowerID:vids[i]
                });
            }
        }
    }
});

Ideally you would use a template helper to retrieve your followers from your collection. Due to these kind of helpers being reactive, you could just call the server method from the client and let the reactivity of Meteor solve your problem of returning the followers via the helper (which is re-executed/re-rendering the template on data change).

Community
  • 1
  • 1
Peter Ilfrich
  • 3,727
  • 3
  • 31
  • 35
  • I have re-factored the code and updated the original question as per your suggestion. I still cannot make it work. I am sure I'm missing something. – roray May 04 '15 at 10:00
  • T in above code is actually npm module "twit". Moreover, `code` T.get('followers/ids', { screen_name: screenname }, function (err, data, response) { console.log(data); }); `code` is having a callback function as `code` function (err, data, response) { console.log(data); } `code` Hence to run it in Meteor, I need to wrap around wrapasync. Please let me know if my understanding is wrong. – roray May 04 '15 at 11:59
  • Changed `code` setTimeout(function() `code` to `code` Meteor.setTimeout(function() `code` and now the code is running beautifully without the server crashing down. @peter, modified your original answer and selected it as answered. Source [link] http://docs.meteor.com/#/full/timers [link] – roray May 04 '15 at 12:28
  • It should be noted that a separate method call is not strictly necessary. You could probably run your code directly inside the `setTimeout` function. – Peter Ilfrich May 04 '15 at 13:21
  • Yes, you are right! So I combined both the method calls into one method as shown: `getTwitterFollowersIDsCollectionsClient : function (screenname){ Meteor.setTimeout(function(screenname){ T.get('followers/ids', { screen_name: screenname }, function (err, data, response) { console.log("from getTwitterFollowersIDsCollectionsClient : "+data.ids); myids=data.ids; return data; }); },10); return; }` Can you please tell me how do I get a handle to the `data` param of the T.get method outside T.get method block. – roray May 04 '15 at 14:58
  • The reason is I want to add the list of follower IDs to Mongo collection. I can't do it inside the T.get method block since I get the same error `Error: Meteor code must always run within a Fiber. Try wrapping callbacks that you pass to non-Meteor libraries with Meteor.bindEnvironment.` – roray May 04 '15 at 15:00
0

try calling:

var wrappedInsert = Meteor.bindEnvironment(function(tweet) {
TweetsCollection.insert(tweet);},
"Failed to insert tweet into Posts collection.");

from inside of api callback

getTwitterFollowersIDsNow : function (screenname) {
  T.get('followers/ids', { screen_name: screenname }, function (err, data, response) {
    for(var i in data)
    {
     wrappedInsert(data[i]);
    }     
  });
}