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);
},
};
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));}