3

Say there is a HTTP GET callback defined as:

router.get('/latestpost', function(req, res, next) {

    var data = new FbData();
    get_latest_post (data);
    get_post_image (data);

    res.json(data);
};

Both get_ functions use the fb package to generate a HTTP request and execute a callback when finished. How can the above GET callback be modified in order to wait for the responses from Facebook and only then send a response to the client?

At the time being I solved the problem by executing the get_ functions in series and passing them the res (response) argument, with the last function sending the response:

router.get('/latestpost', function(req, res, next) {
    var data = new FbData();
    get_latest_post (res, data);
};


function get_latest_post (res, data) {

    FB.api(_url, function (res_fb) {

        if(!res_fb || res_fb.error) {
            console.log(!res_fb ? 'error occurred' : res_fb.error);
            return;
        }

        // Do stuff with data

        get_post_image (res, data);
    });
}


function get_post_image (res, data) {

    FB.api(_url, function (res_fb) {

        if(!res_fb || res_fb.error) {
            console.log(!res_fb ? 'error occurred' : res_fb.error);
            return;
        }

        // Do stuff with data

        /* At the end send the post data to the client */
        res.json(data);
    });
}

I have found a similar question, but I'm wrapping my head around it, since I can't find a proper way to apply the solution to my problem. I have tried using the patterns described in this manual, but I can't get it to execute using promises, or async/await. Can someone please point me in the right direction?

Alexander
  • 921
  • 3
  • 12
  • 31

1 Answers1

6

Your API can easily be modified to return a promise:

 function get_post_image (res, data) {
   return new Promise((resolve, reject) => {
    FB.api(_url, function (res_fb) {
      if(!res_fb || res_fb.error) {
        reject(res_fb && res_fb.error);
      } else resolve(res_fb/*?*/);
   });
 }

Now that you have a promise, you can await it:

 router.get('/latestpost', async function(req, res, next) {
   const data = new FbData();
   const image = await get_post_image (data);

   res.json(data);
});
Jonas Wilms
  • 132,000
  • 20
  • 149
  • 151
  • 2
    Would be good to wrap the `await` in a `try` and pass the caught error to `next()` so you don't have uncaught rejected promises. You can't use `util.callbackify()` though because it would call `next()` with two arguments on success which is not desired. – Patrick Roberts May 13 '19 at 20:25
  • 1
    You _could_ define `const callbackify = fn => Object.defineProperty( (...args) => fn(...args.slice(0, -1)).catch(args.pop()), 'length', { value: fn.length + 1, configurable: true } );` to deal with the way express infers the type of middleware based on the function's arity, and avoid having to explicitly `try...catch` in each `async` middleware. – Patrick Roberts May 13 '19 at 20:38
  • Very true. I might consider that for my next project. – Patrick Roberts May 14 '19 at 20:33
  • @patrick I admit I also haven't used it that much yet ... I'm mostly using Express with some self-baked wrappers around it ... – Jonas Wilms May 14 '19 at 20:34