'How to retrieve all posts of a user via Facebook Graph API using promises and recursion?

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.



Solution 1:[1]

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.

Solution 2:[2]

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.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Jacob
Solution 2