javascriptjsonaws-lambdarssrss-reader

JavaScript: Converting RSS into JSON in AWS Lambda Node app


I'm currently working on writing a function on AWS Lambda. I want to convert a RSS feed into a JSON and give that as a response in the body when making to the Lambda endpoint.

I'm using an npm package to convert the RSS to JSON. However, when I run the code. I see that I get undefined in the conversion of the RSS URL. Here is the following code:

const feed = require('rss-to-json');

exports.handler = async (event) => {
  let rssFeed = event.queryStringParameters.rssFeed;
  let rssAsJsonData = convertRssIntoJson(rssFeed);

  return sendRes(200, rssAsJsonData);
};

const sendRes = (status, body) => {
  var response = {
    isBase64Encoded: true|false,
    statusCode: status,
    headers: {
      "Content-Type": "application/json"
    },
    body: body,
  };
  return response;
};

function convertRssIntoJson (rssFeed) {
  console.log(rssFeed);
  return feed.load(rssFeed, function(err, rss){
    if(err) {
      console.log("Error: ${err}");
      return;
    }
    console.log(rss)
    return rss;
  });
};

However, in the logs I get undefined when console.log(rssAsJsonData). enter image description here

However, when debugging I was able to see console.log(rss) working when I change body to be body: json.stringify("TESTING") enter image description here

However, it only worked when logging to the console not when I tried to pass it to the body body: body, I can't seem to find what the error is. I'm moving from Ruby to JavaScript for this project maybe I'm missing something.

I'm using Postman to make the calls: enter image description here


Solution

  • function convertRssIntoJson (rssFeed) {
      console.log(rssFeed);
      return feed.load(rssFeed, function(err, rss){
        if(err) {
          console.log("Error: ${err}");
          return;
        }
        console.log(rss)
        return rss;
      });
    };
    

    The piece of code above is a callback. Under the hood, feed.load is asynchronous, which makes your callback be executed asynchronously.

    Now, when you invoke your function like this

    let rssAsJsonData = convertRssIntoJson(rssFeed);
    

    your rss object inside convertRssIntoJson does not hold any value yet, because the callback hasn't been populated up to now. This is where your undefined comes from.

    Callbacks themselves don't make code asynchronous by default, but NodeJS works with a non-blocking IO model and, since feed.load is an IO call, it will be executed asynchronously.

    You have a few options now, but I will list only two. A not-so-nice and a nice solution:

    1) The not-so-nice way to fix it is to add a callback as argument to your convertRssIntoJson function and pass the value of that rss object upstream. The not-so-nice full code can be found below:

    const feed = require('rss-to-json');
    
    exports.handler = async (event) => {
        let rssFeed = event.queryStringParameters.rssFeed;
    
        convertRssIntoJson(rssFeed, (err, data) => {
            if (err) {
                return sendRes(500, { message: 'There was an err: ' + err.message })
            }
            return sendRes(200, data)
        })
    };
    
    const sendRes = (status, body) => {
        var response = {
            isBase64Encoded: true | false,
            statusCode: status,
            headers: {
                "Content-Type": "application/json"
            },
            body: body,
        };
        return response;
    };
    
    const convertRssIntoJson = (rssFeed, callback) => {
        console.log(rssFeed);
        feed.load(rssFeed, function (err, rss) {
            if (err) {
                console.log("Error: ${err}");
                callback(err, undefined)
            }
            console.log(rss)
            callback(undefined, rss)
        });
    };
    

    2) The nice, clean, elegant and recommended solution is this one. Wrap your callback in a Promise, like this

    function convertRssIntoJson(rssFeed) {
        console.log(rssFeed);
        return new Promise((res, rej) => {
           feed.load(rssFeed, function (err, rss) {
                if (err) {
                    console.log("Error: ${err}");
                    return rej(err)
                }
                console.log(rss)
                return res(rss)
            });
        })
    };
    

    Since your handler is async, it means it can just await on Promises.

    So your client code is now as simple as:

    return sendRes(200, await convertRssIntoJson(rssFeed));
    

    Your final code will look like (I have refactored a little bit to make use of arrow functions):

    const feed = require('rss-to-json');
    
    exports.handler = async (event) => {
        let rssFeed = event.queryStringParameters.rssFeed;
    
        return sendRes(200, await convertRssIntoJson(rssFeed));
    };
    
    const sendRes = (status, body) => {
        var response = {
            isBase64Encoded: true | false,
            statusCode: status,
            headers: {
                "Content-Type": "application/json"
            },
            body: body,
        };
        return response;
    };
    
    const convertRssIntoJson = (rssFeed) => {
        console.log(rssFeed);
        return new Promise((res, rej) => {
            feed.load(rssFeed, (err, rss) => {
                if (err) {
                    console.log("Error: ${err}");
                    return rej(err)
                }
                console.log(rss)
                return res(rss)
            });
        })
    };
    

    If you want to know more about async/await, you can see it in here.

    EDIT: Code refactor and code added for solution 1)