Search code examples
javascriptnode.jspromisesails.jses6-promise

sails.js controller get data from a promise


I am trying to experiment with sails.js (with node.js). I have a controller as follows. Thsi controller parse an ical url passed in the url param as ical_url and I am trying to parse it and build a normalized json objetc pe rmy need. I have a parse function that is only exposed outside this controller. That uses node-ical npm package (https://www.npmjs.com/package/node-ical) to parse the ical content. In the ical.fromURL method, I have a callback function withing which I initialized a variable normalized_data and storing data in it. My intention to use that data and call to decorate method for some processing and get the json back. Please look at the line starting with //>>>>>> and as per my desire there, I do not have access of normalized_data As per https://github.com/jens-maus/node-ical/blob/HEAD/index.js#L87 ical.fromURL is returning a promise but I am having difficulty understanding how to use the data which is part of that promise. Can someone help me out to make the change needed in this file (especially in the parse method) to meet my expectation? Thanks in advance.

/**
 * IcalsController
 */

const ical = require('node-ical');
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
const days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thur', 'Fri', 'Sat'];

function normalize(ev_obj) {
    return {
        "uid": ev_obj.uid,
        "start": ev_obj.start.toDateString(),
        "end": ev_obj.end.toDateString(),
        "start_ts": ev_obj.start,
        "end_ts": ev_obj.end,
        "user": ev_obj.organizer.val.split("mailto:")[1].split("@")[0], //TODO - use regex for extraction
        "summary": ev_obj.summary,
        "categories": ev_obj.categories,
        "all_day": (ev_obj["event-allday"] == "true")
    }
}

function decorate(data) {
    //do something here and return a json object
    //return json_object;
}

module.exports = {
  parse: function (req, res) {
    if (req.param("ical_url") == undefined) {
        return res.status(400).send("ical_url is missing");
    } 

    ical.fromURL( //refer - https://github.com/jens-maus/node-ical/blob/HEAD/index.js#L87
        req.param("ical_url"),
        {},
        function (err, data) {
          let normalized_data = [];
            for (let k in data) {
              if (data.hasOwnProperty(k)) {
                if (data[k].type == 'VEVENT') {     
                  normalized_data.push(normalize(data[k]));
                }
              }
            }
          }
        );

    //>>>>>> Here I want to call decorate function and pass normalized_data as parameter
    let result = decorate(normalized_data)
    return res.send(result);
  },
};

Solution

  • The problem is from not accounting for the asynchronous nature of the fromURL method.

    The code is calling fromURL, which will result in the callback being called when the method has completed sometime in the future, and then immediately trying to send the data that hasn't been received yet as response to the client.

    You need to send the response after the asynchronous action has completed and you've actually received some data, more like this:

    parse: function (req, res) {
        if (req.param("ical_url") == undefined) {
            return res.status(400).send("ical_url is missing");
        } 
    
        ical.fromURL( //refer - https://github.com/jens-maus/node-ical/blob/HEAD/index.js#L87
            req.param("ical_url"),
            {},
            function (err, data) {
              let normalized_data = [];
                for (let k in data) {
                  if (data.hasOwnProperty(k)) {
                    if (data[k].type == 'VEVENT') {     
                      normalized_data.push(normalize(data[k]));
                    }
                  }
                }
               res.send(decorate(normalized_data)); << Response moved inside the callback.
              }
            );
      }
    

    Though I would actually suggest, you can use the async/await keywords as a much cleaner solution.

    parse: async function (req, res) { << Mark method as async
        if (req.param("ical_url") == undefined) {
            return res.status(400).send("ical_url is missing");
        } 
        // Await keyword lets us use data from a promise more like it were simpler synchronous code
        const raw_data= await ical.fromURL( //refer - https://github.com/jens-maus/node-ical/blob/HEAD/index.js#L87
            req.param("ical_url"),
            {}); 
        let normalized_data = [];
        // Do your normalization of raw_data here
        return res.send(decorate(noramlized_data));}