2

I am currently developing a web app which uses the Facebook Graph API.

What I would like to achieve is to get all posts of a user.

However, this is not that easy since I have to paginate the results.

At the moment I am struggeling with promises.

What I try to achieve is to fill an array with the post objects.

Therefore I use promises and recursion which does not work as expected.

My code currently looks as follows:

// Here I retrieve the user with his or her posts,
// just the first 25 due to pagination
if (accessToken) {
  return new Promise(resolve => {
    FB.api('/me?fields=id,name,posts&access_token=' + accessToken, response => {
      this.get('currentUser').set('content', response);
      resolve()
    })
  })
}

// Returns all posts of a given user
function getAllPostsOfUser(posts, postsArr) {
  // Process each post of the current pagination level
  for (var post of posts.data) {
    // Only store meaningful posts
    if (post !== undefined && post.message !== undefined) {
      postsArr.push(post)
    }
  }

  // Further posts are retrievalable via paging.next which is an url
  if (posts.data.length !== 0 && posts.paging.next !== undefined) {
    FB.api(posts.paging.next, response => {
      getAllPostsOfUser(response, postsArr)
      resolve()
    })
  }

  return postsArr
}

var posts = getAllPostsOfUser(this.get('currentUser').content.posts, [])
// I want to use all the posts here
console.log(posts)

The problem I have is that I want to use the posts where the console.log is placed but when I log the posts array a lot of posts are missing.

I am sure that I did something wrong with the promises but I do not know what.

I would be glad if anyone could guide me to a solution.

Thank you in advance.

Michael Andorfer
  • 1,660
  • 5
  • 25
  • 45

2 Answers2

7

Try this:

function getAllPosts() {
  return new Promise((resolve, reject) => {
    let postsArr = [];
    function recursiveAPICall(apiURL) {
      FB.api(apiURL, (response) => {
        if (response && response.data) {
          //add response to posts array (merge arrays), check if there is more data via paging
          postsArr = postsArr.concat(response.data);
          if (response.paging && response.paging.next) {
            recursiveAPICall(response.paging.next);
          } else {
            resolve(postsArr);
          }
        } else {
          reject();
        }
      });
    }
    recursiveAPICall("/me/posts?fields=message&limit=100");
  });
}

getAllPosts()
  .then((response) => {
    console.log(response);
  })
  .catch((e) => {
    console.log(e);
  });

Not tested, just a quick example I came up with. It returns a promise and uses a recursive function to get all entries. BTW, you don't need to add the Access Token. If you are logged in, the SDK will use it internally.

Jacob
  • 1,577
  • 8
  • 24
andyrandy
  • 72,880
  • 8
  • 113
  • 130
  • I get all posts know but its a bit strange the array contains four objects each of which has some of the posts. – Michael Andorfer Jun 03 '16 at 18:53
  • I see you changed from `push` to `concat` but I do not retrieve any posts with concat whereas I do with `push`. – Michael Andorfer Jun 03 '16 at 18:55
  • concat is for merging arrays. check out what you get with console.log(response.data) right before the concat line. see if all the entries are in there. also, you ma not get ALL entries if your timeline is very long. sooner or later, it may be a problem with api limits...that´s why i set the limit to 100 (default is 25). you may want to limit the amount of API calls later. – andyrandy Jun 03 '16 at 18:56
  • I get just get an empty array before and after the concat. – Michael Andorfer Jun 03 '16 at 18:58
  • then try with console.log(reponse). – andyrandy Jun 03 '16 at 18:59
  • you ALWAYS get something in the response. debug the apiURL, debug console.log(response) as first line after FB.api, there MUST be something in it. – andyrandy Jun 03 '16 at 19:01
  • btw, i edited my answer to include a reject path and i fixed the concat line, it was not entirely correct. of course you get the new array back. – andyrandy Jun 03 '16 at 19:08
  • there is but nothing more than an empty array – Michael Andorfer Jun 03 '16 at 19:09
  • that´s really nothing i can work with. please debug the code, i am using pretty much the same in some projects. the idea is definitely correct, there may be some bugs because i just wrote it quickly in notepad. make sure you are not missing the edits. – andyrandy Jun 03 '16 at 19:09
  • btw, make sure you are authorized correctly too, with FB.login. don´t just use any access token. the response may be empty because you did not authorize correctly - with the user_posts permission, of course. – andyrandy Jun 03 '16 at 19:10
  • the last fix solved the question, each post is available now – Michael Andorfer Jun 03 '16 at 19:11
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/113766/discussion-between-mian-and-luschn). – Michael Andorfer Jun 03 '16 at 19:14
0

This is an old question that is already answered but I thought it could use a more modern answer, considering how many lines of code could be saved. This code has not been tested with the real API but it should work.

This function returns a promise of an array of posts.

async function getPosts(url = "/me/posts?fields=message&limit=100") {
  const { error, paging, data } = (await new Promise(r => FB.api(url, r))) || {}
  if (error || !data) throw new Error(error || "Could not get posts")
  return data.concat(paging?.next ? await getPosts(paging.next) : [])
}

With comments:

async function getPosts(url = "/me/posts?fields=message&limit=100") {
  // get response data out of callback
  const { error, paging, data } = (await new Promise(r => FB.api(url, r))) || {}
  // if there was an error or there wasn't any data, throw
  if (error || !data) throw new Error(error || "Could not get posts")
  // return this page's data + if there's a next page, recursively get its data
  return data.concat(paging?.next ? await getPosts(paging.next) : []) 
}

The function can then be consumed like so:

async function main() {
  try {
    const posts = await getPosts(); // the array of posts
    console.log(posts);
  } catch (error) {
    console.error(error);
  }
}
main();

Below is a snippet demonstrating the function using a fake API.

// fake api for testing purposes
const FB = {
  api(url, callback) {
    const pages = [
      { 
        data: ["post1", "post2", "post3"], 
        paging: { next: 1 },
      },
      { 
        data: ["post4", "post5", "post6"], 
        paging: { next: 2 },
      },
      { 
        data: ["post7", "post8", "post9"], 
      },
    ];
    if (typeof url !== "number") return callback(pages[0]);
    return callback(pages[url]);
  },
};

async function getPosts(url = "/me/posts?fields=message&limit=100") {
  const { error, paging, data } = (await new Promise(r => FB.api(url, r))) || {}
  if (error || !data) throw new Error(error || "Could not get posts")
  return data.concat(paging?.next ? await getPosts(paging.next) : [])
}

async function main() {
  try {
    const posts = await getPosts(); // the array of posts
    console.log(posts);
  } catch (error) {
    console.error(error);
  }
}
main();
.as-console-wrapper{max-height:none !important;top: 0;}

Also see erikhagreis's ESM wrapper on GitHub.

Jacob
  • 1,577
  • 8
  • 24