I'm running a pubsub scheduled function on firebase functions which run every two mintues.
I try to log the events collected by the snapshot loop but something doesn't go properly.
exports.check_event_ended_notification = functions.pubsub.schedule('every 2 minutes').onRun((context) => {
let default_lineup = "default";
let events = [];
return admin.database().ref("Events").once('value')
.then(snapshot => {
console.log(("EVENTS number : " + snapshot.numChildren()));
snapshot.forEach(event_snapshot => {
let event_key = event_snapshot.key;
let event = event_snapshot.val();
events.push(event_key);
console.log("EVENT_UID : " + event_key);
console.log(("EVENTS : " + events));
// iterate through all events and check if they're marked as ended
// if not, check if ended
let event_status = event[['ended_status']];
if (event_status === '0') {
// get event date
let event_day = event[['date']]['0'];
let event_month = event[['date']]['1'];
let event_year = event[['date']]['2'];
let event_full_date = event_year + "-" + event_month + "-" + event_day;
// get today date
let today = new Date();
let today_day = today.getDate();
let today_month = (today.getMonth() + 1);
let today_year = today.getFullYear();
let today_full_date = today_year + "-" + today_month + "-" + today_day;
//create date objects to compare
let event_date = new Date(event_full_date);
let today_date = new Date(today_full_date);
// if event date is in the past
// check to see if its marked as ended.
if (event_date.getTime() < today_date.getTime()) {
console.log("EVENT : past event : " + event_key);
if (event[['ended_status']] === "0") {
console.log("EVENT : ended 0 : " + event_key);
//mark as ended
// admin.database().ref("Events/"+event_key+"/ended_status").set("1");
//create notifications for participation artists
for (let artist_key in event[['lineup']]) {
console.log("ARTIST before: " + artist_key);
if (artist_key !== default_lineup) {
console.log("ARTIST after: " + artist_key);
let approved_invitation = event[['lineup']][artist_key]['approved_invitation'];
let handshake_status = event[['lineup']][artist_key]['handshake_status'];
let quit = event[['lineup']][artist_key]['quit'];
let removed = event[['lineup']][artist_key]['removed'];
let event_publish_status = event[['publish_status']];
let owner_uid = event[['owner_uid']];
if (approved_invitation === '1' && handshake_status === '1' && quit === '0' && removed === '0' && event_publish_status === '1') {
return admin.database().ref("Notifications/" + artist_key + "/" + owner_uid + "/" + event['uid']).push({
notification_type: "ended",
seen_status: "0",
timestamp: new Date().getTime()
})
}
}
}
}
}
}
return null;
})
})
}
while the numChildren() log at the beginning show there are 4 children under the snapshot (which is correct), the foreach() method seems to run only twice, collecting the first two children and adding them to the "events" list.
and the logs stop after this:
2020-07-29T14:22:00.593Z ? check_event_ended_notification: EVENTS number : 4
2020-07-29T14:22:00.593Z ? check_event_ended_notification: EVENT_UID : 3853c2db-f31a-4f46-8c1b-740ca4e3407b
2020-07-29T14:22:00.593Z ? check_event_ended_notification: EVENTS : 3853c2db-f31a-4f46-8c1b-740ca4e3407b
2020-07-29T14:22:00.594Z ? check_event_ended_notification: EVENT_UID : 4253c2db-f31a-4f46-8c1b-740ca4e3407s
2020-07-29T14:22:00.594Z ? check_event_ended_notification: EVENTS : 3853c2db-f31a-4f46-8c1b-740ca4e3407b,4253c2db-f31a-4f46-8c1b-740ca4e3407s
You are not correctly managing the life cycle of your Cloud Function. As explained in the doc, you need to "resolve functions that perform asynchronous processing (also known as "background functions") by returning a JavaScript promise".
Also, in a Cloud Function, it is not recommended to use the on() method, which sets a listener, but the once() one, which "listens for exactly one event of the specified event type, and then stops listening.".
So the following should do the trick:
exports.check_event_ended_notification = functions.pubsub.schedule('every 2 minutes').onRun((context) => {
let default_lineup = "default";
let events = [];
return admin.database().ref("Events").once('value') // Note the return here
.then(snapshot => {
snapshot.forEach(event_snapshot => {
let event_key = event_snapshot.key;
let event = event_snapshot.val();
events.push(event_key)
console.log("EVENT_UID : " + event_key);
console.log(("EVENTS : " + events));
});
// Do something with events... We don't have enough details
return null; // Or return a promise
});
});
Update following your comment
If you want, in the forEach(), to write to the database (which is an asynchronous process), you should use Promise.all(), in order to return a promise when all the parallel calls to the asynchronous push() method are done.
Here is a skeleton of the code, that you should adapt with all the details:
exports.check_event_ended_notification = functions.pubsub.schedule('every 2 minutes').onRun((context) => {
let default_lineup = "default";
let events = [];
return admin.database().ref("Events/").once('value')
.then(snapshot => {
const promises = [];
snapShot.forEach(event_snapshot => {
let event_key = event_snapshot.key;
let event = event_snapshot.val();
events.push(event_key)
console.log("EVENT_UID : " + event_key);
console.log(("EVENTS : " + events));
promises.push(
admin.database().ref("Notifications/" + artist_key + "/" + owner_uid + "/" + event['uid']).push({
notification_type: "ended",
seen_status: "0",
timestamp: new Date().getTime()
})
);
});
return Promise.all(promises); // This returns a promise!!
});
});
Related
This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 2 years ago.
Trying to find a value in an excel file using the XLSX library:
The function works, it finds the value, however the output is undefined, even though the debugging say the value is found.
Here's the function:
var getValsFromExcel = function(sheet,idcol, valcol, val){
var workbook = new Excel.Workbook();
workbook.xlsx.readFile(__dirname + '/assets/gu.xlsx')
.then(function() {
var worksheet = workbook.getWorksheet(sheet);
worksheet.eachRow({ includeEmpty: false }, function(row, rowNumber) {
console.log("Row " + rowNumber + " = " + JSON.stringify(row.values));
console.log(row.values[idcol]);
console.log('checking ' + row.values[idcol] + ' = ' + val + ' ' + (row.values[idcol] == val))
if (row.values[idcol] == val){
console.log('Value found! its ' + row.values[valcol])
//getValsFromExcel = row.values[valcol];
return row.values[valcol];
}
});
});
}
var ans = getValsFromExcel('yesno',3, 4, tobj["respondent_consent"]);
console.log('Q1 answer = ' + ans);
Here's the console output:
Q1 answer = undefined
Row 1 = [null,"UID","Delete(Y/N)","field: yesno_key_value","field: yesno_display_text"]
field: yesno_key_value
checking field: yesno_key_value = yes false
Row 2 = [null,"5b45fe42f7fe481d8442d5e94b894b45","N","yes","Si"]
yes
checking yes = yes true
Value found! its Si
Row 3 = [null,"b65ba5a1a3814a87b4571e8d477307aa","N","no","No"]
no
checking no = yes false
getValsFromExcel asynchronous, here is the correction:
var getValsFromExcel = function(sheet,idcol, valcol, val){
var workbook = new Excel.Workbook();
return workbook.xlsx.readFile(__dirname + '/assets/gu.xlsx')
.then(function() {
var worksheet = workbook.getWorksheet(sheet);
let answer = null;
worksheet.eachRow({ includeEmpty: false }, function(row, rowNumber) {
console.log("Row " + rowNumber + " = " + JSON.stringify(row.values));
console.log(row.values[idcol]);
console.log('checking ' + row.values[idcol] + ' = ' + val + ' ' + (row.values[idcol] == val))
if (row.values[idcol] == val){
console.log('Value found! its ' + row.values[valcol])
//getValsFromExcel = row.values[valcol];
answ = row.values[valcol];
return;
}
});
return answer;
});
}
getValsFromExcel('yesno',3, 4, tobj["respondent_consent"])
.then( answer => console.log('Q1 answer = ' + ans) );
There are a couple things going on here. First, you're not returning any value from getValsFromExcel, so doing var ans = getValsFromExcel() will always be undefined (the default return value of any function).
But even if you do return workbook.xlsx.readFile(__dirname + '/assets/gu.xlsx').then() // ... you won't get the value you have in console.log('Value found! its ' + row.values[valcol]), because workbook.xlsx.readFile returns a Promise.
If you're on a recent version of Node, you can add that return before your readFile call, and then do this
async function main() {
var ans = await getValsFromExcel()
console.log(ans)
}
main()
Edit: sorry for the half answer initially. I don't like the SO editor and apparently I smashed a key combo that prematurely saved it.
Here's another solution showing this without async/await. Remember: async/await is just Promises with special syntax, but it works the same.
getValsFromExcel(/* args */)
.then((value) => {
console.log(value)
})
This is the same thing. We either have to await the Promise, or we have to chain a then with a callback that will be invoked with the value you return in your Promise chain in the getValsFromExcel function.
There were a number of bugs in the original code... here's a further breakdown for completeness' sake:
const Excel = require("exceljs")
var getValsFromExcel = function (sheet, idcol, valcol, val) {
var workbook = new Excel.Workbook()
return workbook.xlsx
.readFile(__dirname + "/assets/gu.xlsx")
.then(function () {
var worksheet = workbook.getWorksheet(sheet)
// let's initialize with some value.
// undefined would work as well, but we can be explicit if it's not found
// and make it null.
let result = null
worksheet.eachRow({ includeEmpty: false }, function (row, rowNumber) {
if (row.values[idcol] == val) {
// ok now we reassign result to the value we want
result = row.values[valcol]
}
})
// In order to have access to the value in the next `then`
// of your Promise chain, you _must_ return it.
return result
})
.then((value) => {
console.log("Value = " + value)
})
}
// alternate version using async/await
var getValsFromExcel = async function (sheet, idcol, valcol, val) {
var workbook = new Excel.Workbook()
// wait for the asynchronous code to resolve
await workbook.xlsx.readFile(__dirname + "/assets/gu.xlsx")
// after this point, workbook has been mutated and now contains the file's data
var worksheet = workbook.getWorksheet(sheet)
let result = null
worksheet.eachRow({ includeEmpty: false }, function (row, rowNumber) {
if (row.values[idcol] == val) {
result = row.values[valcol]
}
})
console.log("Value = " + result)
}
getValsFromExcel("Sheet1", 2, 2, "Dulce")
I am trying to access my API with a loop as this is one of my requirements. The code i have written is below.
currentWeekStartDate = (new Date(requestStartDate));
currentWeekEndDate = (new Date(requestEndDate));
let currentWeekStartDateNew = (new Date(requestStartDate));
let currentWeekEndDateNew = (new Date(requestEndDate));
console.log("testing ZONE");
/*test zone */
let outerEventArray = [];
while (currentWeekStartDateNew.getDate() !==currentWeekEndDateNew.getDate()){
console.log(currentWeekStartDateNew);
let startTIme = new Date(currentWeekStartDateNew);
let endTime = new Date(currentWeekEndDateNew);
startTIme.setHours(startHour,startMinute,0,0);
endTime.setHours(endHour,endMinute,0,0)
while(startTIme.getHours() !== endTime.getHours()){
let newStartTime = new Date(startTIme);
newStartTime.setHours(startTIme.getHours() + 1);
let firstTimeArg = startTIme.toISOString().replace("Z", "0000");
let secondTimeArg = newStartTime.toISOString().replace("Z", "0000");
let returnVal = getEventArray(access_token, firstTimeArg,secondTimeArg).then(res => {
return res;
});
if(returnVal !== null){
console.log("returnVal");
console.log(returnVal);
outerEventArray.push(returnVal);
console.log("returnVal");
}
/*
function
*/
startTIme = new Date(newStartTime);
}
currentWeekStartDateNew.setDate(currentWeekStartDateNew.getDate() + 1);
}
console.log("outerEventArray");
console.log(outerEventArray);
console.log("outerEventArray");
and my function for the API call is
getEventArray = async(access_token, startArguement,endArguement) => {
let eventVal;
let urlVal = "https://graph.microsoft.com/v1.0/users/" + user + "/calendarview?startdatetime=" + startArguement + "&enddatetime=" + endArguement + "";
request.get({
url: urlVal,
headers: {
"Authorization": "Bearer " + access_token
}
}, function (err, response, body) {
let bodyJSON = JSON.parse(body);
eventVal = bodyJSON.value;
if(eventVal == null){
console.log("eventVal is null");
}
else{
let eventArray = [];
for (let valueVal of Object.values(eventVal)) {
let eventStartTime = valueVal.start.dateTime;
let eventEndTime = valueVal.end.dateTime;
if (!((eventStartTime[eventStartTime.length - 1] === 'Z') || (eventStartTime[eventStartTime.length - 1] === 'z'))) {
eventStartTime = eventStartTime + "Z";
}
if (!((eventEndTime[eventEndTime.length - 1] === 'Z') || (eventEndTime[eventEndTime.length - 1]) === 'z')) {
eventEndTime = eventEndTime + "z"
}
eventArray.push({ start: new Date(eventStartTime).toLocaleString(), end: new Date(eventEndTime).toLocaleString() });
}
console.log(eventArray);
return eventArray;
}
});
}
However i am not able to get the outerEventArray value outside. My requirement is to access is outside with all the data populated. Is there any way this is possible?
I have below function to update rows if meeting some condition and at the end of for loop, response include how many rows updated.Despite more than zero rows updated, response shows zero. Looking at log, it seems reponse.success() fires before completing for loop.
why so?
Parse.Cloud.define("reset", function(request, response) {
var isSaveNeeded = false
var Query = new Parse.Query("price");
Query.equalTo('isActive', true);
Query.find({useMasterKey:true})
.then((results) => {
console.log("Found " + results.length + " price rows")
var currentDate = moment()
var noOfRowsUpdated = 0
for (let i = 0; i < results.length; ++i) {
var valid_till_date = results[i].get('valid_till_date');
if (valid_till_date == null) {
// if user had not selected valid_till_date then set to expire after system max no of days
var updatedAt = results[i].get('updatedAt');
if (currentDate.diff(updatedAt,'days') > 10) {
console.log("Permanent change row to be set inactive. Updated at - " + currentDate.diff(updatedAt)+ updatedAt)
results[i].set('isActive',false)
isSaveNeeded = true
}
} else if (currentDate.diff(valid_till_date) > 0) {
// check whether valid_till_date has passed
console.log("Found row with elapsed valid date " + results[i].id)
results[i].set("isActive",false)
isSaveNeeded = true
}
if (isSaveNeeded == true) {
console.log("Record needs to be saved for " + results[i].id)
results[i].save(null, {useMasterKey:true})
.then(function (user) {
++noOfRowsUpdated
console.log("reset : Object ID: " + results[i].id + " saved - " + noOfRowsUpdated)
})
.catch(function (error) {
console.log("reset : Error saving Object ID: " + results[i].id + error);
response.error(error);
})
} else {
console.log("Record not to be saved for " + results[i].id)
}
isSaveNeeded = false
} // end of for loop
//BELOW IS EXECUTED BEFORE FOR LOOP COMPLETES
console.log("Updated " + noOfRowsUpdated +" price rows");
response.success("Updated " + noOfRowsUpdated +" price rows")
}) // end of .then((results)
.catch(function(error) {
response.error("Failed to fetch from price" + error );
});
});
Parse.com's save runs async, so that loop finishes before the saves happen. The solution is to reorganize the code a little bit, and wait for the saves to happen before executing the response functions.
The trick is to collect the promises returned by each save in an array and wait for the fulfillment of those promises with Promise.when() (synonym for Promise.all())
To make it clearer, factor out the "is save needed" logic, so this cloud function can be only about handling the database...
Parse.Cloud.define("reset", function(request, response) {
var Query = new Parse.Query("price");
Query.equalTo('isActive', true);
Query.find({useMasterKey:true}).then((results) => {
console.log("Found " + results.length + " price rows");
// assuming ES6 or something like underscore
let pricesToSave = results.filter(price => priceNeedsSave(price));
// here's the important part, collect the promise from each save
// proceed only after the promises have completed
let promises = pricesToSave.map(price => price.save(null, {useMasterKey:true}));
return Parse.Promise.when(promises).then(() => pricesToSave.length);
}).then(count => {
response.success("Updated " + count +" price rows");
}).catch(error => {
response.error("Failed to fetch from price" + error );
});
}
Just for completeness, below is the factored-out needsSave logic. (OP should check this over, I just copied the body of the loop)...
function priceNeedsSave(price) {
var isSaveNeeded = false;
var currentDate = moment()
var valid_till_date = price.get('valid_till_date');
if (valid_till_date == null) {
// if user had not selected valid_till_date then set to expire after system max no of days
var updatedAt = price.get('updatedAt');
if (currentDate.diff(updatedAt,'days') > 10) {
console.log("Permanent change row to be set inactive. Updated at - " + currentDate.diff(updatedAt)+ updatedAt)
price.set('isActive',false)
isSaveNeeded = true
}
} else if (currentDate.diff(valid_till_date) > 0) {
// check whether valid_till_date has passed
console.log("Found row with elapsed valid date " + price.id)
price.set("isActive",false)
isSaveNeeded = true
}
return isSaveNeeded;
}
I have a very simple timeInterval observable and I want to start/stop transmission without disconnecting subscribers (which should sit and wait regardless of observable status). Is possible, and if so how?
var source = Rx.Observable
.interval(500)
.timeInterval()
.map(function (x) { return x.value + ':' + x.interval; })
.take(10);
var subscription = source.subscribe(
function (x) {
$("#result").append('Next: ' + x + ' ');
},
function (err) {
$("#result").append('Error: ' + err);
},
function () {
$("#result").append('Completed');
});
general comment: most of the examples ive seen show how to define observables and subscribers. how do i affect the behavior of existing objects?
Depends on what is the source of the stop/resume signal. The simplest way I can think about is with the pausable operator, which as the documentation says works better with hot observables. So in the following sample code, I removed the take(10) (your pausable signal now comes through the pauser subject), and added share to turn your observable into a hot one.
About hot vs. cold, have a look to the illustrated respective data flows.
On subjects, you can also review the corresponding semantics
var pauser = new Rx.Subject();
var source = Rx.Observable
.interval(500)
.timeInterval()
.map(function (x) { return x.value + ':' + x.interval; })
.share()
.pausable(pauser);
var subscription = source.subscribe(
function (x) {
$("#result").append('Next: ' + x + ' ');
},
function (err) {
$("#result").append('Error: ' + err);
},
function () {
$("#result").append('Completed');
});
// To begin the flow
pauser.onNext(true); // or source.resume();
// To pause the flow at any point
pauser.onNext(false); // or source.pause();
Here is a more sophisticated example which will pause your source every 10 items:
// Helper functions
function emits ( who, who_ ) {return function ( x ) {
who.innerHTML = [who.innerHTML, who_ + " emits " + JSON.stringify(x)].join("\n");
};}
var pauser = new Rx.Subject();
var source = Rx.Observable
.interval(500)
.timeInterval()
.map(function (x) { return x.value + ':' + x.interval; })
.share();
var pausableSource = source
.pausable(pauser);
source
.scan(function (acc, _){return acc+1}, 0)
.map(function(counter){return !!(parseInt(counter/10) % 2)})
.do(emits(ta_validation, 'scan'))
.subscribe(pauser);
var subscription = pausableSource.subscribe(
function (x) {
$("#ta_result").append('Next: ' + x + ' ');
},
function (err) {
$("#ta_result").append('Error: ' + err);
},
function () {
$("#ta_result").append('Completed');
});
You should have by now your answer to the second question. Combine the observables you are given with the relevant RxJS operators to realize your use case. This is what I did here.
not the most elegant, but probably the simplest:
timeSubscription: Subscription
timer: Observable<number>;
time = 0;
toggle() {
if (!this.timer)
this.timer = interval(500);
if (!this.timeSubscription || this.timeSubscription.closed)
this.timeSubscription = this.timer.subscribe(tick => { // running
console.log(this.time++);
});
else
this.timeSubscription.unsubscribe(); // not running
}
var yModule = require('youtube-node'),
nodeYoutube = new yModule();
nodeYoutube.setKey("key");
module.exports.getVideoLength = function (vData){
youTube.getById(vData, function (result) {
return convertTime(result['items'][0]['contentDetails']['duration']);
})
};
var convertTime = function (time){
var reptms = /(?:(\d+)DT)?(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?$/;
var days = "00", hours = "00", minutes = "00", seconds = "00", formattedTime;
//if (reptms.test(time)) {
var matches = reptms.exec(time);
console.log(matches);
if (matches[1]) days = String(matches[1]);
if (matches[2]) hours = String(matches[2]);
if (matches[3]) minutes = String(matches[3]);
if (matches[4]) seconds = String(matches[4]);
formattedTime = "[" + days + ":" + hours + ":" + minutes + ":" + seconds + "]";
return formattedTime;
//}
};
I'm struggling to understand callbacks even after reading a few things about it.
nodeJs callbacks simple example this helped a little, but I'm still unclear about how it works. I've spent the past hour trying to figure out how to write this using callbacks.
This module is being called by this:
ytRetrieve.getVideoLength(youtube_parser(text))
youtube_parser's function:
function youtube_parser(url){
var regExp = /^.*((youtu.be\/)|(v\/)|(\/u\/\w\/)|(embed\/)|(watch\?))\??v?=?([^#\&\?]*).*/;
var match = url.match(regExp);
//console.log(match);
if (match&&match[7]){
return match[7].split(" ")[0];
}
}
You need to use callbacks. The issue with your code youtube_parser( is that you are calling the function. A callback is a function that is passed as an argument to be called later. If you call the function, a string is returned. getVideoLength is expecting a function as an argument, not a string.
Instead use getVideoLength(youtube_parser). This actually passes in the youtube_parser function itself to be called later (i.e. when getVideoLength completes). The arguments to youtube_parser may need to be (error, url) instead, though.
Here is a solution I came up with. Is there anything I can do to enhance this code?
Thank you for your help.
Main.js
var ytempRetrieve = require('./youtube'), ytRetrieve = new ytempRetrieve();
var ytRegex = /(?:https?:\/\/)?(?:www\.)?(?:youtu\.be\/|youtube\.com\/(?:embed\/|v\/|watch\?v=|watch\?.+&v=))((\w|-){11})(?:\S+)?/;
bot.addListener('message', function (from, to, text, message) {
if (text.match(ytRegex)) {
console.log(text);
youtube_parser(text, to, ytRetrieve.getVideoLength)
}
});
function youtube_parser(url, to, callback) {
var regExp = /^.*((youtu.be\/)|(v\/)|(\/u\/\w\/)|(embed\/)|(watch\?))\??v?=?([^#\&\?]*).*/;
var match = url.match(regExp);
//console.log(match);
if (match && match[7]) {
callback(match[7].split(" ")[0], function (res) {
setTimeout(function () {
bot.say(to, match[7].split(" ")[0] + " is " + res + " long.")
}, 1500)
});
}
}
youtube.js
var yModule = require('youtube-node'),
nodeYoutube = new yModule(),
apiKey = require('./config');
var youtube = function () {
var self = this;
self.time = null;
self.setAPIKey = function (key) {
nodeYoutube.setKey(key);
};
apiKey.getAPIKey(self.setAPIKey);
self.getVideoLength = function (vData, callback) {
nodeYoutube.getById(vData, function (result) {
callback(self.convertTime(result['items'][0]['contentDetails']['duration']));
})
};
self.convertTime = function (time) {
var reptms = /(?:(\d+)DT)?(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?$/;
var days = 0, hours = 0, minutes = 0, seconds = 0, formattedTime;
//if (reptms.test(time)) {
var matches = reptms.exec(time);
console.log(matches);
if (matches[1]) days = Number(matches[1]);
if (matches[2]) hours = Number(matches[2]);
hours += days * 24;
if (hours.toString().length === 1) {
hours = "0" + hours
}
if (matches[3]) minutes = String(matches[3]);
if (minutes.toString().length === 1) {
minutes = "0" + minutes
}
if (matches[4]) seconds = String(matches[4]);
if (seconds.toString().length === 1) {
seconds = "0" + seconds
}
formattedTime = "[" + hours + ":" + minutes + ":" + seconds - 1 + "]";
return (formattedTime);
//}
};
};
module.exports = youtube;