-1

I am trying to use a promise to call a function getTweets. Not using an AJAX call, but a simple promise 'call' from 1 javascript file to another. The function works, but i keep getting 'undefined'. I have read dozens of questions here on stackoverflow and have spent days to understand promises, but still can't solve it.

var Twit = require('twit') // Imports the Twitter library
require('dotenv').config() // to get the environment vars into the app
// This is the function:
function getTweets (screen_name) {
  let T = new Twit({ /* <twitter key and token here> */ });
  T.get('statuses/user_timeline', { screen_name: screen_name, count: 3}, function (err, data, response) {
    let myTweets = [];
    for (let i in data) {
      let text = data[i].text; 
      myTweets.push(text); 
    }
    return myTweets;
  })
}
 module.exports.getTweets = getTweets;

And this is the promise that tries to get the tweets:

 var promise = tweets.getTweets('dizid');
 promise.then(
     console.log(myTweets), 
     console.log(err))
 // gives error: promise.then(
 //                      ^
 // TypeError: Cannot read property 'then' of undefined

Any help greatly appreciated.

Lennholm
  • 7,205
  • 1
  • 21
  • 30
  • in your question something is missing on top – Rahul Sharma Feb 14 '18 at 17:46
  • what's `T`? and where is the `getTweets` function defined? – nitely Feb 14 '18 at 17:49
  • Uh, your `getTweets` function is *not* using promises, that's why you get an error when treating the return value as a promise? – Bergi Feb 14 '18 at 17:54
  • Do you understands how (asynchronous) callbacks work and could you solve your problem with them alone? – Bergi Feb 14 '18 at 17:55
  • Sorry, i thought we are not supposed to give ALL code: function getTweets (screen_name) { // Instantiates a Twitter client with variables from the .env file const T = new Twit({ consumer_key: process.env.CONSUMER_KEY, etcetc – Marc de Ruyter Feb 14 '18 at 18:16
  • @Bergi i am trying for days, almost 2 weeks now to learn js/node/async etc. I come from old skool PHP/MySQL. Maybe i'm too old for this. – Marc de Ruyter Feb 14 '18 at 18:18
  • @nitely const T = new Twit is a new Twitter instance, just above the code. Also, the function is defined just above the code: function getTweets (screen_name) { etc – Marc de Ruyter Feb 14 '18 at 18:20
  • The promise i try to use is in antoher js file, because i try to code modular in node. – Marc de Ruyter Feb 14 '18 at 18:21
  • @MarcdeRuyter If you're having trouble with the syntax of ECMAScript and how to use asynchronous then maybe the [following book](https://github.com/getify/You-Dont-Know-JS#titles) will help. – HMR Feb 14 '18 at 19:16
  • @MarcdeRuyter without the code (or a minimal version of it), everyone is just guessing. – nitely Feb 14 '18 at 19:20
  • @MarcdeRuyter You're not supposed to give *all* code, you're supposed to give all *relevant* code. The definition of `getTweets()` is very relevant, since that's where the issue is (from the error message, we can deduce that the issue is most likely that instead of returning a promise, `getTweets()` doesn't return anything). Please edit your question and add the missing code, don't type it in comments. – Lennholm Feb 14 '18 at 19:52
  • 1
    Possible duplicate of [How do I return the response from an asynchronous call?](https://stackoverflow.com/questions/14220321/how-do-i-return-the-response-from-an-asynchronous-call) – Lennholm Feb 14 '18 at 20:53
  • @MikaelLennholm this is all the code i have (except that i got it working that i do a sentiment analysis on the tweets with the Google NL API, but first i need to solve this). I really did everything i could to solve this: read dozens of similar questions on stackoverflow, study and try dozens of articles about promises. – Marc de Ruyter Feb 14 '18 at 22:50
  • @HMR Thank you, will look into that. The 3 versions of JS doesn't make it easier to follow online tutorials. I did an udemy JS course for a week. I do have 5+ years programming experience, and i do feel pretty studpid right now. – Marc de Ruyter Feb 14 '18 at 22:50
  • @MarcdeRuyter With node you can mostly stick to ES6 and some ES7 for newer versions without the need to convert. You can also try babel to change the code you wrote to an older version of ES before running it (good example [here](https://github.com/babel/example-node-server)). When you're just starting with ES it may feel like a lot to learn (language, core types and tool chain) but it's worth the effort. – HMR Feb 15 '18 at 03:34

4 Answers4

1

Your problem is that you never return anything from your getTweets() function even though it needs to return a promise. The function calls T.get() and pass it a callback function. You return from this callback function but this doesn't do anything, it doesn't mean that this value gets returned from getTweets().

This is a pretty common mistake when it comes to working with asynchronous calls. What needs to be done is to make getTweets() return a promise that gets resolved when it should.
When working with asynchronous calls that don't implement the promise interface, you need to wrap this call with a new promise. Your getTweets() function should then look like this:

function getTweets (screen_name) {
  let T = new Twit({ /* <twitter key and token here> */ });
  return new Promise(function(resolve, reject) {
    T.get('statuses/user_timeline', { screen_name: screen_name, count: 3}, function (err, data, response) {
      if (err) {
        reject(err); // Reject the promise since there was an error
      } else {
        let myTweets = [];
        for (let i in data) {
          let text = data[i].text; 
          myTweets.push(text); 
        }
        resolve(myTweets); // Resolve the promise with the result
      }
    });
  });
}

However, it seems the Twit API does support the promise interface, so instead of providing a callback function you can just use the promise created by T.get(). HMR's answer explains how to do this.

Another mistake you've made is with this code:

promise.then(
  console.log(myTweets), 
  console.log(err))

The way you've written it, it reads "Run console.log(myTweets) and console.log(err), then invoke promise.then() with the result of the former as the first argument and the result of the latter as the second argument.
then() takes callback functions (which get invoked depending on the resolving/rejection of the promise) as arguments, so the code should look like this:

promise.then(
  function(myTweets) { 
    console.log(myTweets);
  },
  function(err) { 
    console.log(err);
  });

Async/await

If you're interested in taking things further, the modern approach to working with asynchronous code is async/await, which is syntactic sugar for promises that lets you write asynchronous code more similar to regular synchronous code.

A function marked as async will implicitly return a promise, but you write it as if you return a regular value. Using the await keyword inside an async function will implicitly wait for a promise to resolve and unwrap the resolved value. The main practical benefits of this is that you can use asynchronous calls in loops and handle errors with regular try-catch blocks. Your getTweets() function would look like this using async/await:

async function getTweets(screen_name) {
  let T = new Twit({ /* <twitter key and token here> */ });
  const data = await T.get('statuses/user_timeline', { screen_name: screen_name, count: 3});
  // Let's also use map() instead of a for loop
  let myTweets = data.map(function(item) { return item.text; });
  return myTweets;
}
Lennholm
  • 7,205
  • 1
  • 21
  • 30
  • Although technically correct answer (and +1), @HMR answer presents much simpler approach, based on a fact that `T.get` can already return a promise when invoked without a callback. Thus, manually promisyfing it could be considered unnecessary. – Wiktor Zychla Feb 15 '18 at 12:38
  • @WiktorZychla I know, which is why I referred to HMR's answer rather than just reiterating the same thing. The point of my answer is to provide more of a general explanation on how promises work in order to help with understanding them better, since that's something OP seems to want. – Lennholm Feb 15 '18 at 13:13
  • @MikaelLennholm Thanks alot for you explanation indeed. This also have put me on the right path and i'm sure i will get it to work eventually, at least i understand what is going on and the differences between the approaches. for now, i get "ReferenceError: myTweets is not defined", but i'm sure i can solve that. – Marc de Ruyter Feb 15 '18 at 17:41
  • Solved! got it, after more then a week of puzzling, thanks. Will try the async/await method also! – Marc de Ruyter Feb 15 '18 at 21:08
0

Since get seems to return a promise you don't need to use a callback. Get Tweets can look something like this:

//  in getTweets
return T.get(
  'statuses/user_timeline', 
  { screen_name: screen_name, count: 3}
).then(
  function (data) {
    console.log("data:",JSON.stringify(data,undefined,2));
    return data.map(item=>item.text);
  }
)
// exports the function getTweets so that other modules can use it
module.exports.getTweets = getTweets;

If that didn't work please let us know what the output of the program is (update question).

You can call getTweets like so:

tweets.getTweets('dizid')
.then(
  myTweets=>
    console.log(myTweets),
  err=>
    console.log(err)
)
HMR
  • 37,593
  • 24
  • 91
  • 160
  • Thank you @HMR, i will start working with this early in the morning. – Marc de Ruyter Feb 14 '18 at 22:35
  • Thanks again, this certainly has put me on the right path. When i call the getTweets function i get alot of (raw) twitter data back (much more then just the tweet objects) but i can solve that later. Near the end of the (twitter data) output i get an error: "TypeError: data.map is not a function" but i think i will solve that too. This is the closest i got to a solution, so i will mark this as an Answer. I will work on this even if it costs me weeks. At least i learned alot. – Marc de Ruyter Feb 15 '18 at 17:36
-1

I think you forget add function like

promise.then(function(res){
    //code
}
Pablo DbSys
  • 532
  • 1
  • 7
  • 17
-2

Your .then() should include a call back function.

promise.then( res => {
    console.log(res);
});

edit: I'm using an ES6 syntax for arrow functions, in case you're new to that.

  • I tried your sugestion, but i am still getting "TypeError: Cannot read property 'then' of undefined". I have tested the code inside my function (just as a script, not as a function) and that works: i get 3 tweets back and 'console.log' verified that. The problem start when i turned my 'getTweets' code into a function and try to call that function from another script. I am bot new to JS and node and the error msgs are not very helpfull either. – Marc de Ruyter Feb 14 '18 at 20:41