Is there a way to cancel a promise? - node.js

This code works well.
I have a list of available campaigns and another list of active users. For each user, I need to check which campaigns have been shown before. I would like to know if it is possible to dispense with my promise to write undefined in my return matrix.
Is there a way to cancel a promise?
In this case, if there is already a record in the redis node, the answer will be equal to one and I would like to cancel the promise to avoid undefined return.
How to fixed this?
VerificarCampanhaEnvio : async function(user,campanhas){
const CampanhaUsers = [];
let sismember = util.promisify(cliente_redis.sismember).bind(cliente_redis);
return Promise.all(campanhas.map(async(campanha) =>{
return sismember("subscription:"+user.id,campanha.id_campanha).then((response)=>{
console.log('response',user.id,response,campanha.id_campanha);
if(response != 1){
data = {};
data.user = user;
data.campanha = campanha;
return data;
}
});
})).then((response) => {
return response;
});
},
FireMessage : function(){
Sucesso = function(data,campanhas){
if(campanhas.length > 0){
self.GetInscritosAtivos(function(users){
if(users !== false){
var contador = 1;
users.map(async(user) =>{
var finalArray;
console.log('user.id',user.id);
finalArray = await self.VerificarCampanhaEnvio(user,campanhas);
var filtered = finalArray.filter(function(el){ return el != null; });
if(filtered.length > 0) self.EnviarMensagem(filtered[0]);
});
}else console.log('fnpush:FireMessage','Nao existe subscricoes ativas');
});
}else console.log('fnpush:FireMessage','Nao existem campanhas de push disponiveis no
sistema.');
}
data = {};
data.zona = {};
data.zona.tipo = 'push';
data.sucesso = Sucesso;
data.falha = fnCampanha.CampanhaInexistente;
fnCampanha.GetCampanhas(data);
}

Related

Firebase Function throws returned undefined, expected Promise or value

My below firebase function throws the error Function returned undefined, expected Promise or value. I am doing a return everywhere and not sure why it still throws this error
I have looked into similar code samples and not sure what point does it throw the error.
exports.on_order_updated_update_new_vs_repeat_order = functions.database.ref("/orders/{id}")
.onUpdate((change, context) => {
const newValue = change.after.val();
const oldValue = change.before.val()
//if order is marked delivered then update the data
if(oldValue.order._orderStatus !== 'Delivered' && newValue.order._orderStatus === 'Delivered'){
//find the uid of the customer
const uid = newValue.customer._uid
var isOldOrder = false //to track weather the customer is new or repeat
var db = admin.database();
var ref = db.ref('users')
return ref.child(uid).child('orders').once("value").then(
(resp) => {
const orderKeys = Object.keys(resp.val())
if(orderKeys.length > 1)
isOldOrder = true //existing orders there so just set it to true
var date = new Date()
var begDate = findDayBegninning(date)
var endDate = findDayEnd(date)
var anaRef = db.ref('analytics')
return anaRef.child('newVsRepeatOrders').orderByChild("date").startAt(begDate).endAt(endDate).once("value").then(
(rp) => {
if(rp !== undefined && rp.val() !== null){
const newOldObj = rp.val()
var oldOrderVal = 0
var newOrderVal = 0
if(isOldOrder === true){
oldOrderVal = newOldObj[begDate].oldOrdersCount + 1
newOrderVal = newOldObj[begDate].newOrdersCount
}
return anaRef.child('newVsRepeatOrders/' + begDate).update({"oldOrdersCount": oldOrderVal, "newOrdersCount": newOrderVal}).then(
(resp1) => console.log("updated order count")
).catch(
(err) => console.error("error in updating old vs new order count:" + err)
)
}else{
console.log("no data found for today so adding first record")
var oldOrderCount = 0
var newOrderCount = 0
if(isOldOrder === true)
oldOrderCount++
else
newOrderCount++
var payload = {
"date" : begDate,
"oldOrdersCount": oldOrderCount,
"newOrdersCount" : newOrderCount
}
return anaRef.child('newVsRepeatOrders/' + begDate).set(payload).then(
(rpp) => console.log("updated newVsRepeatOrders")
).catch(
(err) => console.error("Error updating newVsRepeatOrders::" + err)
)
}
}
).catch(
(err) => console.error("Could not execute path newVsRepeatOrders for the customer uid:" + uid + " error is:" + err)
)
}
).catch(
(err) => console.error("Could not find orders for the customer uid:" + uid + " error is:" + err)
)
}
}
)
Please ignore the text from here. stackoverflow does not let me post saying i only have code and add text.
You're not returning anything from the function in the event that the top level condition is false. Let me compress your function down so you can see more clearly:
exports.on_order_updated_update_new_vs_repeat_order = functions.database.ref("/orders/{id}")
.onUpdate((change, context) => {
const newValue = change.after.val();
const oldValue = change.before.val()
//if order is marked delivered then update the data
if(oldValue.order._orderStatus !== 'Delivered' && newValue.order._orderStatus === 'Delivered'){
// return some promise...
}
// nothing is returned if the above condition was false
}
)
You need to return something in every case. If your function has no async work to do in case the main condition is false, just return null.
As a matter of better style and clarity, you may also wish to chain your promises rather than nesting them. Nested promises are difficult to read, and linters may complain about that, as they are also prone to error.

Cheerio.load messes with responses for Google Assistant

I have this intent that has a cheerio.load() being called and it messes with with the responses. The Google Assistant keeps telling me that no response has been set even though later down in the code I have responses. The console is also telling me that an async call wasn't being returned to the handler, which I believe is the cheerio.load(). Is there anyway I can fix this so that it continues looking for the correct conv.ask at the bottom of the code? It still continues to run down to there too, for the console.log(map) shows up. Thanks for any help!
app.intent("late drop", (conv,{term,year}) => {
var date = new Date();
var month;
if(term == null){
month = date.getMonth();
if(month >= 9 && month <=12){
term = "fall";
//console.log("fall")
}
else if (month >= 1 && month <= 5) {
term = "spring";
//console.log("spring")
}
else {
term = "summer";
//console.log("summer")
}
}
if(year == null){
yearDig = date.getFullYear();
year = yearDig;
//console.log(year)
}
var strYear = year.toString();
var semester = term+strYear.substr(2);
const options = {
uri: `https://www.registrar.psu.edu/academic_calendar/${semester}.cfm`,
transform: function (body) {
return cheerio.load(body);
}
};
rp(options)
.then(($) => {
let map = {};
let columnOne = [];
let columnThree = [];
$('table').find('tr td:nth-child(1)').each(function (index, element) {
columnOne.push($(element).text());
});
$('table').find('tr td:nth-child(3)').each(function (index, element) {
columnThree.push($(element).text());
});
columnOne.forEach((item, i) => {
map[item] = columnThree[i];
});
console.log(map);
date = map["2Late Drop Begins"];
conv.ask("The late drop period begins on " + map["2Late Drop Begins"])
})
.catch((error) => {
console.log(error);
conv.ask("An error occured, please try again.");
})
});
The issue is not with cheerio.
It looks like you are using request-promise or request-promise-native to make your HTTP call. This does an asynchronous operation that will return a Promise (as is evidenced by your use of .then() and .catch().
Since Intent Handlers that do asynchronous operations must return a Promise, you can simply return the one that is returned by the rp/then/catch chain. Something like changing this line should work:
return rp(options)
I have modified your code to return a Promise. Check if this works for you.
app.intent("late drop", (conv, {
term,
year
}) => {
return new Promise(function (resolve, reject) {
var date = new Date();
var month;
if (term == null) {
month = date.getMonth();
if (month >= 9 && month <= 12) {
term = "fall";
//console.log("fall")
} else if (month >= 1 && month <= 5) {
term = "spring";
//console.log("spring")
} else {
term = "summer";
//console.log("summer")
}
}
if (year == null) {
yearDig = date.getFullYear();
year = yearDig;
//console.log(year)
}
var strYear = year.toString();
var semester = term + strYear.substr(2);
const options = {
uri: `https://www.registrar.psu.edu/academic_calendar/${semester}.cfm`,
transform: function (body) {
return cheerio.load(body);
}
};
rp(options)
.then(($) => {
let map = {};
let columnOne = [];
let columnThree = [];
$('table').find('tr td:nth-child(1)').each(function (index, element) {
columnOne.push($(element).text());
});
$('table').find('tr td:nth-child(3)').each(function (index, element) {
columnThree.push($(element).text());
});
columnOne.forEach((item, i) => {
map[item] = columnThree[i];
});
console.log(map);
date = map["2Late Drop Begins"];
conv.ask("The late drop period begins on " + map["2Late Drop Begins"])
resolve()
})
.catch((error) => {
console.log(error);
conv.ask("An error occured, please try again.");
reject()
})
});
});
Hope that helps!

Firebase cloud functions realtime db parallel requests

I have an issue with handling parallel requests with cloud functions.
My scenario is to select a driver from the db and update its status.
I do check for that status property before updating it, but when I send multiple requests (database on create triggers to be specific) within a second it doesn't seem to read the updated status property. And it always updates with the information of the last request. I also have noticed that sometimes the requests are processed altogether.
What can I do to fix these issues?
index.js
const db = app.database();
const TripManagementUtil = require('./utils').TripManagementUtil;
exports.triggerNotifications = functions.database.ref('/Trip/{pushId}').onCreate( (snapshot, context) =>
{
var newTrip = snapshot.val();
var tripKey = context.params.pushId;
var tripManagementUtil = new TripManagementUtil();
tripManagementUtil.searchDrivers(tripKey, newTrip, db);
});
utils.js
searchDrivers(tripKey, trip, db){
const results = [];
var lat = trip.pickupLocation.lat, long = trip.pickupLocation.lng;
var vehicleTypeID = trip.vehicleTypeID;
var alreadyAssigned = trip.alreadyAssigned;
var self = this;
if(alreadyAssigned == null || alreadyAssigned == 'undefined'){
alreadyAssigned = [];
}
const geofireQuery = new GeoFire(db.ref('vehicleLocation').child(vehicleTypeID + "")).query({
center: [lat, long],
radius: constants.searchRadius
})
.on('key_entered', (key, coords, distance) => {
if(alreadyAssigned.indexOf(key) == -1){
var result = {
driverID: key,
distance: distance
}
results.push(result);
}
});
setTimeout(() => {
geofireQuery.cancel();
if (results.length === 0) {
self.noDriversHandler(alreadyAssigned, tripKey, db);
} else {
results.sort((a, b) => a.distance - b.distance);
var driversAvailable = false;
var index = 0;
function checkDriver(){
db.ref().child("driver").child("available").child(results[index].driverID).once('value').then(function(vehicleSnap){
var vehicle = vehicleSnap.val();
if(!driversAvailable){
if(vehicle != null && vehicle.vehicleTypeID == vehicleTypeID
&& (vehicle.tripStatus != TripVehicleActionEnum.DriverConfirmed && vehicle.tripStatus != TripVehicleActionEnum.VehicleAssigned)
&& alreadyAssigned.indexOf(vehicle.driverID +"") === -1){
driversAvailable = true;
self.driverExistsHandler(trip, tripKey, alreadyAssigned, vehicle, db);
}
if(!driversAvailable && index + 1 == results.length){
self.noDriversHandler(alreadyAssigned, tripKey, db);
}
index++;
}
else{
index++;
checkDriver();
}
});
}
checkDriver();
}
}, 1500);
}
To write data to the database where the value is based on an existing value, you'll want to use Firebase Realtime Database transactions. For more on this, and examples, see save data transactionally in the Firebase documentation.

node wait for iteration to complete before callback

I have a lambda function in node.js to send a push notification.
In that function I need to iterate through my users sending a notification for each one prior to the callback.
Ideally I would like the iteration to perform in parallel.
What would be the best way to do this?
My code is currently as follows but it does not work as expected because the last user is not always the last to be handled:
var apnProvider = new apn.Provider(options);
var iterationComplete = false;
for (var j = 0; j < users.length; j++) {
if (j === (users.length - 1)) {
iterationComplete = true;
}
var deviceToken = users[j].user_device_token;
var deviceBadge = users[j].user_badge_count;
var notification = new apn.Notification();
notification.alert = message;
notification.contentAvailable = 1;
notification.topic = "com.example.Example";
apnProvider.send(notification, [deviceToken]).then((response) => {
if (iterationComplete) {
context.succeed(event);
}
});
}
Use Promise.all instead - map each user's associated apnProvider.send call to a Promise in an array, and when all Promises in the array are resolved, call the callback:
const apnProvider = new apn.Provider(options);
const userPromises = users.map((user) => {
const deviceToken = user.user_device_token;
const deviceBadge = user.user_badge_count;
const notification = new apn.Notification();
notification.alert = message;
notification.contentAvailable = 1;
notification.topic = "com.example.Example";
return apnProvider.send(notification, [deviceToken]);
})
Promise.all(userPromises)
.then(() => {
context.succeed(event);
})
.catch(() => {
// handle errors
});

Proper way to make callbacks async by wrapping them using `co`?

It is 2016, Node has had nearly full ES6 support since v4, and Promises have been around since 0.12. It's time to leave callbacks in the dust IMO.
I'm working on a commander.js-based CLI util which leverages a lot of async operations - http requests and user input. I want to wrap the Commander actions in async functions so that they can be treated as promises, and also to support generators (useful for the co-prompt library I'm using for user input).
I've tried wrapping the CB with co in two ways:
1)
program.command('myCmd')
.action(program => co(function* (program) {...})
.catch(err => console.log(err.stack)) );
and
2) program.command('myCmd').action(co.wrap(function* (program) { .. }));
The problem with 1) is that the program parameter isn't passed
The problem with 2) is that errors are swallowed...
I'd really like to get this working as it yields much nicer code in my use case - involving a lot of http requests and also waiting for user input using the co-prompt library..
Is it a better option altogether perhaps to wrap program.Command.prototype.action somehow?
thanks!
I've used a bespoke version of something like co to get a db.exec function which uses yield to do database request. You can pass parameters into a generator function (I pass in a connection object - see the comment where I do it).
Here is by db.exec function that is very similar to what co does
exec(generator) {
var self = this;
var it;
debug('In db.exec iterator');
return new Promise((accept,reject) => {
debug('In db.exec Promise');
var myConnection;
var onResult = lastPromiseResult => {
debug('In db.exec onResult');
var obj = it.next(lastPromiseResult);
if (!obj.done) {
debug('db.exec Iterator NOT done yet');
obj.value.then(onResult,reject);
} else {
if (myConnection) {
myConnection.release();
debug('db.exec released connection');
}
accept(obj.value);
debug('db.exec Promise Resolved with value %d',obj.value);
}
};
self._connection().then(connection => {
debug('db.exec got a connection');
myConnection = connection;
it = generator(connection); //This passes it into the generator
onResult(); //starts the generator
}).catch(error => {
logger('database', 'Exec Function Error: ' + error.message);
reject(error);
});
});
}
the connection object also wraps by database connection object and provides a generator function ability to process the rows of the results from the database, but I won't post that here (although the example below is using it to process the rows).
Here is an example of using the exec function to run a sequence of sql
db.exec(function*(connection) {
if (params.name === ADMIN_USER) {
debug('Admin Logon');
user.name = ADMIN_DISPLAY;
user.keys = 'A';
user.uid = 0;
let sql = 'SELECT passwordsalt FROM Admin WHERE AdminID = 0';
connection.request(sql);
yield connection.execSql(function*() {
let row = yield;
if (row) {
user.nopass = (row[0].value === null);
} else {
user.nopass = false;
}
debug('Admin Password bypass ' + user.nopass.toString());
});
} else {
debug('Normal User Logon');
let sql = `SELECT u.UserID,PasswordSalt,DisplayName,AccessKey,l.LogID FROM Users u
LEFT JOIN UserLog l ON u.userID = l.userID AND DATEDIFF(D,l.LogDate,GETDATE()) = 0
WHERE u.UserName = #username`;
let request = connection.request(sql);
request.addParameter('username',db.TYPES.NVarChar,params.name);
let count = yield connection.execSql(function*() {
let row = yield;
if (row) {
user.uid = row[0].value;
user.name = row[2].value;
user.keys = (row[3].value === null) ? '' : row[3].value;
user.nopass = (row[1].value === null) ;
user.lid = (row[4].value === null) ? 0 : row[4].value;
debug('Found User with uid = %d and lid = %d, keys = %s',
user.uid, user.lid, user.keys);
}
});
if (count === 0) {
debug('Not Found User');
// couldn't find name in database
reply(false,false);
return;
}
}
if (!user.nopass) {
debug('Need a Password');
//user has a password so we must check it
passGood = false; //assume false as we go into this
let request = connection.request('CheckPassword');
request.addParameter('UserID',db.TYPES.Int,user.uid);
request.addParameter('password',db.TYPES.VarChar,params.password);
yield connection.callProcedure(function*() {
let row = yield;
if (row) {
//got a valid row means we have a valid password
passGood = true;
}
});
} else {
passGood = true;
}
if (!passGood) {
debug('Not a Good Pasword');
reply(false,true);
} else {
if (user.uid !== 0 && user.lid === 0) {
let sql = `INSERT INTO UserLog(UserID,LogDate,TimeOn,UserName) OUTPUT INSERTED.logID
VALUES(#uid,GETDATE(),GETDATE(),#username)`;
let request = connection.request(sql);
request.addParameter('uid',db.TYPES.Int,user.uid);
request.addParameter('username',db.TYPES.NVarChar,user.name);
yield connection.execSql(function*() {
let row = yield;
if (row) {
user.lid = row[0].value;
debug('Users Log Entry = %d',user.lid);
}
});
}
reply(true,user);
}
})
.catch((err) => {
logger('database','Error on logon: ' + err.message);
reply(false,false);
});
});
There is a quite simple way to do async function in Commander.js
async function run() {
/* code goes here */
}
program
.command('gettime')
.action(run);
program.parse(process.argv);

Resources