My Twitterbot and promises

Hi I’m writing a Twitter bot started from this tutorial: hackermoon.com

For the life of me I can’t get the Twitter data as a global var [followerList] to show outside of the GET method. Will someone give it a quick read? Thank you.

var twit = require('twit');
var redis = require('redis'), client = redis.createClient();
var config = require('./config.js');
var Twitter = new twit(config);

follower = {id: '', screen_name: '', name: '', verified: false,
            followers_count: ''};
followerList = [];


async function getFollowersById( screenName ) {
   Twitter.get('followers/ids', {screen_name: screenName})
   .catch(function (err) {
      console.log('Caught error:', err.stack);
   })
   .then (function (result) {
      for(i = 0; i < result.data.ids.length; i++) {
         follower.id = result.data.ids[i];
         followerList.push(follower);
         console.log(follower);
      }
   })
   .then ( (followerList) => new Promise((resolve) => resolve(followerList)) )
}

getFollowersById( 'DevorrahTester' ).then( (result) => {
   console.log('RESULT', result);
   console.log("FL: ", followerList);
});
client.quit();


//////////////////////////////////////////////////////////////////
returns:
/Twitterbot$ node bot.js
RESULT undefined
FL:  []
{ id: 989512528177827800,
  screen_name: '',
  name: '',
  verified: false,
  followers_count: '' }
{ id: 3126866326,
  screen_name: '',
...
...
//////////////////////////////////////////////////////////////////////

1 Like

Because the contents of getFollowersById are asynchronous.

getFollowersById( 'DevorrahTester' ).then( //This will execute immediately, because everything inside getFollowersById is asynchronous.

You can see that in your results. FL: printed out before the Followers.

1 Like

To express it a bit more wordily…and probably a bit more metaphorically than actually occurs, but it gets the point across.

Your main script (when it begins execution) says…

  1. “Go call getFollowerById. I’ll wait.”
  2. getFollowersById says “I’m Asynchronous, so i’ll hand back a promise to get things done. Continue on your way.”
  3. the “Main” thread then executes client.quit(), and waits for that promise to be completed.
  4. Meanwhile, the getFollowersById thread says “Okay, I need to run this get function of a Twitter object.”
  5. The twitter object says “Get is an asynchronous function. Here, have a promise.” It then wanders off to Twitter to do its thing.
  6. The getFollowersById thread immediately says “Great. I’ve finished my synchronous commands. Main task, my Promise is completed.”
  7. Main task receives the signal that the Promise is complete, and runs it’s Then.
  8. At some point, anytime before, during, or after #'s 6 and 7, the Get function comes back and says “I’m done, here’s your stuff.”
  9. The getFollowersById thread says "Ah my promise (#5) is ready. Run my Then’s. (and/or catch, if something bad happened.)
1 Like

I don’t think you’re using the async function correctly. Declaring a function async is usually coupled with an await to wait for a variable.

more reading: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function#Description

Mutating a global variable from within a promise like this, is also kind of bad practice.

It also looks like you’re trying to use the follower variable as a template, but that’s not going to work because you will be copying by reference, so you’re essentially storing the same variable in every instance of the array. If you want to use a template, you either need to use the object spread operator or Object.assign to copy the object by value before assigning a new variable.

See this fiddle I made for clarification on what’s going to happen if you fix your async error: https://jsfiddle.net/2s3hso3s/

Try something like this:

const twit = require('twit')
const config = require('./config.js')
const Twitter = new twit(config)

function getFollowersById(screenName) {
    return new Promise(resolve => {
        Twitter.get('followers/ids', {screen_name: screenName})
        .then(result => {
            const followers = result.data.ids.map(id => {id})
            resolve(followers)
        })
        .catch(err => {
            console.error(`Caught error: ${err.stack}`)
        })
    })
}

async function main() {
    const followers = await getFollowersById('DevorrahTester')
    console.log(`FL: ${JSON.stringify(followers)}`)
}

main()

The above code is untested, but it should be closer to what you’re looking for. It doesn’t use your json object template, but you can add that if you feel like you need it. Usually in json you leave keys undefined if they don’t have a value.

Also: Not sure why you’re using Redis here. For a bot, Redis is probably way overkill. Redis is usually used as a shared cache between multiple instances. If you’re trying to cache something, you’re probably going to be better off just using local memory instead of wasting a network call (plus the code complexity) to hit Redis.

2 Likes

Thank you @m_hutley. That was an excellent explanation.

Thank you @mawburn. Your code is exactly what I was going for. As for Redis, I’m only using it because it is easy. Do you think I should use mysql database instead?

1 Like

Depends on what you’re doing with it.

If you’re looking for persistent data storage, then a database would be better for that anyway. You might also be able to use SQLite or even a .json file, if you don’t want to go for the full MySQL/PostgreSQL setup.

If you’re looking for a cache, you’d be better off with using memory. You don’t need Redis unless you have several of these bots that all need to share a cache.

I C. That makes a lot of sense. SQLite it is. Thanks.

1 Like

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.