简体   繁体   中英

Why do i get undefined, even when using a promise?

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.

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;
}

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)
)

I think you forget add function like

promise.then(function(res){
    //code
}

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM