I want to update the list of "Reservations" with some new variables. But I can't make it work because the function ends before updating. I know the problem is because I make asynchronous calls but I can't solve it. How can I use Promise in this case?
I edited the code using Promise but still does not update the "Reservations"
Returns {"data": null} to my website console
In the firebase console does not generate any errors. Return the message "reservations actualized"
exports.actualizeReservations = functions.https.onCall((data, response) => {
var promisesUpdateReservations = [];
return admin.database().ref(Constants.RESERVATIONS).once("value")
.then((snapshot) => {
console.log("RESERVATIONS: " + snapshot)
snapshot.forEach((reserveSnap) => {
var reserve = reserveSnap.val();
if(reserve.reservationId && reserve.restoId) {
admin.database().ref(`${Constants.RESTAURANTS}/${reserve.restoId}`).once("value")
.then((restoSnap) => {
if(restoSnap.exists() && restoSnap.val() !== null) {
var resto = restoSnap.val();
if(resto.address && !resto.address.fullAddress) {
var restoAddress = resto.address ? {
street: resto.address.street ? resto.address.street : "",
fullAddress: resto.address.fullAddress ? resto.address.fullAddress : "",
city: resto.address.city ? resto.address.city : "",
country: resto.address.country ? resto.address.country : "",
postalCode: resto.address.postalCode ? resto.address.postalCode : "",
province: resto.address.province ? resto.address.province : "",
l: resto.l ? resto.l : null,
g: resto.g ? resto.g : null,
} : null;
var restoUserIDs = `${reserve.restoId}/${reserve.userId}`;
const promiseUpdateReservation = admin.database().ref(`${Constants.RESERVATIONS}/${reserve.reservationId}`).update({
restoAddress,
restoUserIDs
})
promisesUpdateReservations.push(promiseUpdateReservation)
}
}
})
.catch((err) => {
console.log("resto not found")
});
}
});
Promise.all(promisesUpdateReservations)
.then(() => {
console.log("reservations actualized");
return { code: 0, result: "successful", description: "reservations actualized" };
})
})
.catch((err) => {
console.log("get restaurants error: " + err);
return { code: 1, result: "successful", description: "reservations not loaded" };;
});
})
This function is going to complete immediately every time it's invoke, and likely perform none of its work. This is because it's not returning anything at all from the top-level function callback. Instead, you need to return a promise from the top level of the function that resolves with the data to send to the client only after all the async work is complete (and you're kicking off a lot of it here). Proper handling of promises is critical to making Cloud Functions work correctly.
Related
I have an issue here where the code is quite heavy and quite hard to read imo.
I've simplified the code as much as I can but I was wandering if there's a way to simplify this code even further? Or maybe if there is any better terminology i could use for the comments? Any help is much appreciated, Thanks in advance!
const hourly = rateType[1] === 'Hourly';
const daily = rateType[1] === 'Day Pass';
const monthly = rateType[1] === 'Monthly Pass';
const single = passType === 'single';
// -- For all payloads
const data = {
booking_name: username.first_name + ' ' + username.last_name,
number_of_people: numOfPeople,
};
// -- Hourly payload
const hourlyPayload = {
...data,
date: moment(mainDate).format('YYYY-MM-DD'),
from: moment(timeFrom).format('hh:mm'),
to: moment(timeUntil).format('hh:mm'),
};
// -- Daily or monthly payload
const DayOrMonthPayload = {
...data,
start_date: moment(startDate).format('YYYY-MM-DD'),
end_date: moment(endDate).format('YYYY-MM-DD'),
};
// -- Single day payload
const singleDayPayload = {
...data,
dates: [moment(startDate).format('YYYY-MM-DD')],
};
// -- // CREATE_BOOKING_FN // -- //
if (rateType) {
setLoading(true);
hourly // --||-- Hourly Action --||-- \\
? await addToCart(hourlyPayload, 'hourly', id, cartItems, () =>
_handleAdded(
fastCheckout ? fastCheckout : null,
cb ? () => cb() : null,
),
)
: daily // --||-- Daily Action --||-- \\
? await addToCart(
single ? singleDayPayload : DayOrMonthPayload,
single ? 'individual-dates' : 'daily',
id,
cartItems,
() =>
_handleAdded(
fastCheckout ? fastCheckout : null,
cb ? () => cb() : console.log('null'),
),
)
: monthly // --||-- Monthly Action --||-- \\
? await addToCart(DayOrMonthPayload, 'monthly', id, cartItems, () =>
_handleAdded(
fastCheckout ? fastCheckout : null,
cb ? () => cb() : console.log('null'),
),
)
: null;
setLoading(false);
} else {
alert('Please select a rate');
}
You can use Template Strings to simplify booking_name
booking_name: `${username.first_name} ${username.last_name}`,
Also consistency in variable names would better, you could choose one of the variable naming styles, snake_case or camelCase.
Also now you can shorten expression key:value even more.
const data = {
booking_name: `${username.first_name} ${username.last_name}`,
number_of_people,
};
Also that ternary expression is very hard to read, I think switch case would better.
switch (type_of_date) {
case hourly:
...
case daily:
...
case monthly:
...
default:
...
}
I would recommend using functions for avoiding repetition and creating data. Here we have a basic Booking object that can be used to construct all varieties of bookings. Fill in ___ as you see fit -
function Booking(type, ___) {
switch (type) {
case "Hourly"
return { ...Party(___), ...Hourly(___) }
case "DayOrMonth":
return { ...Party(___), ...DayOrMonth(___) }
case Single:
return { ...Party(___), ...Single(___) }
default:
throw Error("invalid booking type: " + type)
}
}
In the function above it's plain to see that each output has Party information associated -
function Party(user, number_of_people) {
return {
name: user.first_name + " " + user.last_name
number_of_people
}
}
Here are the booking types, Hourly, DayOrMonth and Single -
function Hourly(date, from, to) {
return {
date: mDate(date),
from: mTime(from),
to: mTime(to)
}
}
function DayOrMonth(start, end) {
return {
start: mDate(start),
end: mDate(end)
}
}
function Single(date) {
return {
date: mDate(date)
}
}
We avoid repetition of moment(...).format(...) and ensure consistency with mDate and mTime -
function mDate(t) {
return moment(t).format("YYYY-MM-DD")
}
function mTime(t) {
return moment(t).format("hh:mm")
}
Description
I've got a response coming back from a Service which I am calling on my NODE back-end.
I am then transforming my response in a more friendly format, like so;
export default (response) => {
const {
limit,
debt,
outstanding
} = response.object;
const transformedData = {
outstanding: retrieveAmounts(outstanding),
limit: retrieveAmounts(limit),
debt: retrieveAmounts(debt)
};
return _omitBy(transformedData, value => value === null);
};
As you can see I am running a function retrieveAmounts(value) on each item.
const retrieveAmounts = ({ amount, code }) => ({
amount: isStringDefined(amount) ? amount : null,
currencyCode: isStringDefined(code) ? code : null
});
Update
retrieveAmounts() in turn calls isDefinedString which checks the type and length provided, like so;
const isDefinedString = value => typeof value === 'string' && value.length > 0;
The problem is that while both; debt and limit are required and will always return - outstanding is not. If there is nothing to display the object key will simply not be there.
This is when the retrieveAmounts() throws an error because it cannot fin amount or code of undefined - since they don't exist because the key is not there.
How can I format the object key if it exists but return null if it does not? This way, retrieveAmounts() will not throw an error and I will simply provide limit:null to my front-end app.
'undefined' cannot be destructured. You need to check whether it's undefined before destructuring.
let limit = {'amount': '1000', 'code': '£'}, outstanding = undefined, debt = {'amount': '900', 'code': '£'};
const retrieveAmounts = amountStruct => {
if (typeof amountStruct === 'undefined')
return null;
let {amount, code} = amountStruct;
return {
amount: isStringDefined(amount) ? amount : null,
currencyCode: isStringDefined(code) ? code : null
};
};
Inline following #alex35's code.
const retrieveAmounts = (amountStruct) => ((typeof amountStruct === 'undefined') ? null : {
amount: isStringDefined(amountStruct.amount) ? amountStruct.amount : null,
currencyCode: isStringDefined(amountStruct.code) ? amountStruct.code : null
});
You could put a default parameter inside your retrieveAmounts function, so putting undefined inside your retrieveAmounts function doesn't produce an error.
const retrieveAmounts = (outstanding = { amount: null, code: null }) => ({ // << here
amount: isStringDefined(outstanding.amount) ? amount : null,
currencyCode: isStringDefined(outstanding.code) ? code : null
});
So when I'm on a Book Page, and click a button to delete it, it should redirect to my User Profile and delete the Book from the database. But what happens is ComponentWillMount is called again for the Book and it can't find it. Maybe I don't have a clear understanding of these lifecycle methods but I thought ComponentWillMount will only be called once when first rendering? Also using Firebase for the database.
Here's the relevant code (where Book == WIP):
WIP.js
componentWillMount() {
this.WIPRef.on('value', snapshot => {
let WIP = snapshot.val()
this.setState({
title: WIP.title ? WIP.title : "",
wordCount: WIP.wc ? WIP.wc : "",
logline: WIP.logline ? WIP.logline : "",
draft: WIP.draft ? WIP.draft : "",
language: WIP.language ? WIP.language : "",
disclaimers: WIP.disclaimers ? WIP.disclaimers : "",
improvementAreas: WIP.improvementAreas ? WIP.improvementAreas : "",
blurb: WIP.blurb ? WIP.blurb : "",
additionalNotes: WIP.additionalNotes ? WIP.additionalNotes : "",
writer: WIP.writer ? WIP.writer : "",
genres: WIP.genres ? WIP.genres : [],
types: WIP.types ? WIP.types : []
});
var promises = []
var writerRef = firebaseDB.database().ref(`/Users/${WIP.writer}`)
promises.push(writerRef.once('value'));
Promise.all(promises).then((snapshots) => {
snapshots.forEach((snapshot) => {
var writer = snapshot.val()
this.setState({
writerName: writer.displayName ? writer.displayName : ""
})
})
})
})
}
removeWIP(WIPId) {
this.setState({ redirect: true })
const usersWIPRef = firebaseDB.database().ref(`/Users/${this.state.writer}/WIPs/${this.state.wipId}`)
this.deleteWIPIndexRecord(this.state.wipId)
usersWIPRef.remove();
this.WIPRef.remove();
}
deleteWIPIndexRecord(wipId) {
const WIPRef = firebaseDB.database().ref(`WIPs/${wipId}`);
WIPRef.on('value', snapshot => {
// Get Algolia's objectID from the Firebase object key
const objectID = snapshot.key;
// Remove the object from Algolia
wipsIndex
.deleteObject(objectID)
.then(() => {
console.log('Firebase object deleted from Algolia', objectID);
})
.catch(error => {
console.error('Error when deleting contact from Algolia', error);
process.exit(1);
});
})
}
render() {
if (this.state.redirect === true) {
return <Redirect to= {{pathname: '/user/' + this.state.writer}} />
}
return(
<Button className="black-bordered-button"
onClick={() => this.removeWIP(WIP.id)}
>
Remove Item
</Button>
)
}
But then when I delete the book I get this error:
Uncaught TypeError: Cannot read property 'title' of null from this line in ComponentWillMount title: WIP.title ? WIP.title : ""
I am using googles api to get 3 fields,
name rating and place review. Since place review uses another api call I created it as a function to get the data inside my place_review.
response.results.forEach((entry)=>{
var restaurantName = {
"name" : entry.name,
"rating" : entry.rating,
"place_review" : placeReview(entry.place_id)
}
arr.push(restaurantName);
console.log(restaurantName);// shows only name and rating
});
The extra function
function placeReview(place_id){
console.log(place_id) // i see all place id via forloop.
googlePlaces.placeDetailsRequest({placeid: place_id},function(error,response){
if (error) throw error;
return response.result.reviews[0].text;
});
}
When I run the code , i get name : somename, rating : 4.5 and place_review : null
I thought the forloop will call the function and the return of the function will append the string to "place_review" not sure what I am doing wrong.
The placeDetailsRequest api is asynchronous, meaning it is initiated but doesn't complete right away. Consequently, the line:
return response.result.reviews[0].text;
is meaningless because there is nothing to return to.
Your console.log(restaurantName); will be run before the request completes so you will only get an 'undefined'.
To fix this, you will need to do something a fair bit more complicated ( will assume, for now that Promises are something you are not familiar with but that is typically the way people go once they are comfortable with the way asynchronous programming works):
response.results.forEach((entry)=>{
var restaurantName = {
"name" : entry.name,
"rating" : entry.rating,
"place_review" : undefined // will be filled in later
}
placeReview(entry.place_id, restaurantName);
});
function placeReview(place_id, obj){
console.log(place_id) // i see all place id via forloop.
googlePlaces.placeDetailsRequest({placeid: place_id},function(error,response){
if (error) throw error;
obj.place_review = response.result.reviews[0].text;
console.log(obj);
});
}
Of course, you will only see the console results as each request finishes.
This uses a technique called 'callbacks' to manage the information that is only available at a later time. There is lots of information about this online and on SO.
Here's a version that you can run to see the technique in action - very similar to above but hard-wired some data.
var list = [
{ name: 'Aaron', rating: '14', place_id: 21 },
{ name: 'Brad', rating: '33', place_id: 33 }
];
list.forEach(function (entry) {
var restaurantName = {
"name" : entry.name,
"rating" : entry.rating,
"place_review" : undefined // will be filled in later
};
placeReview(entry.place_id, restaurantName);
});
function placeDetailsRequest(idObj, cb) {
setTimeout(function() {
var result = (idObj.placeid === 21) ? 'def' : 'abc';
cb(null, { result: { reviews: [{ text: result}]}});
}, 2000);
}
function placeReview(place_id, obj) {
console.log(place_id); // i see all place id via for loop.
placeDetailsRequest( { placeid: place_id }, function(error,response){
if (error) throw error;
obj.place_review = response.result.reviews[0].text;
console.log(obj);
});
}
Further Edit to Show Promise Solution:
var list = [
{ name: 'Aaron', rating: '14', place_id: 21 },
{ name: 'Brad', rating: '33', place_id: 33 }
];
var allPromises = list.map(function (entry) {
var restaurantName = {
"name" : entry.name,
"rating" : entry.rating,
"place_review" : undefined // will be filled in later
};
return placeReview(entry.place_id, restaurantName);
});
Promise.all(allPromises)
.then(results => console.log(results))
.catch(error => console.log(error));
function placeReview(place_id, obj) {
return new Promise((resolve, reject) => {
// Here's where you would do your google api call to placeDetailsRequest
// if error then reject the promise, otherwise resolve it with your results
//googlePlaces.placeDetailsRequest({placeid: place_id},function(error,response){
// if (error) {
// reject(error);
// } else {
// obj.place_review = response.result.reviews[0].text;
// resolve(obj);
// }
//});
// simulating with delayed response
setTimeout(() => {
var result = (place_id === 21) ? 'def' : 'abc';
obj.place_review = result;
resolve(obj);
}, 2000);
});
}
There are other ways to approach this, but in this solution I am resolving the promise with the completed object. The Promise.all() function then either shows the successful results or the catch will display an error.
Promises
You could use Q in order to implement promises in your place review function:
function placeReview (place_id) {
var deferred = Q.defer();
googlePlaces.placeDetailsRequest({placeid: place_id},function(error,response){
if (error) deferred.reject(err);
else deferred.resolve(response.result.reviews[0].text);
});
return deferred.promise // the promise is returned
}
then you can invoke that function in your processing logic like this:
response.results.forEach((entry)=>{
placeReview(entity.place_id).then(function(text) {
var restaurantName = {
"name" : entry.name,
"rating" : entry.rating,
"place_review" : text
}
arr.push(restaurantName);
console.log(restaurantName);// shows only name and rating
});
});
you will not push the restaurant details till your call to the API is complete.
Async option
You could use async to coordinate your calls:
This function will be executed once per each entry:
function placeReview(entry, callback){
console.log(entry.place_id)
googlePlaces.placeDetailsRequest({entry.placeid: place_id},function(error,response){
if (error) throw error;
//this call back retrives the restaurant entity that async stack in a list
callback(null,{
"name" : entry.name,
"rating" : entry.rating,
"place_review" : response.result.reviews[0].text
});
});
}
Then you ask async to iterate your result array and execute placeReview for each one. Async will gather results in an array:
async.map(response.results, placeReview, function(err, results) {
// results will have the results of all 2
console.log(results); //this is your array of restaurants
});
I would like to access the node "status" and check: if false writes data, if true returns error alert.
I used the code:
var reference_value_of_counter = firebase.database().ref('contador/valor');
var reference_to_increment_counter = firebase.database().ref('contador/valor');
var reference_status_attendance = firebase.database().ref('agendamentos/primeiro-horario/status');
var reference_to_data_register = firebase.database().ref('agendamentos/primeiro-horario/');
if (reference_status_attendance.once('value', snap => snap.val() == false)) {
reference_value_of_counter.once('value', snap => reference_to_increment_counter.set(snap.val()+1));
reference_to_data_register.update({nome : 'isaac', sobrenome : "menezes", status: true});
}
else {
alert("sorry");
}
if (reference_status_attendance.once('value', snap => snap.val() == false))
This statement is not correct. once function wouldn't return the value that you return from your callback. You need to access the value and perform the if/else inside the callback. I haven't tested the below code but it should work or at least give you an idea of what needs to be done.
reference_status_attendance.once('value', snap => {
if(snap.val() == false) {
reference_value_of_counter.once('value', snap => reference_to_increment_counter.set(snap.val()+1));
reference_to_data_register.update({nome : 'isaac', sobrenome : "menezes", status: true});
}
else {
alert("sorry");
}
})
See Firebase Documentation
var reference_to_data_register = firebase.database().ref('agendamentos/primeiro-horario/');
reference_to_data_register.once('value')
.then(function(dataSnapshot) {
if(dataSnapshot.val().status){
//do this if status is true
} else {
// do this is status is false
}
});