Resolving error 500; "Cannot access 'object' before initialization" } - javascript

Looking for help on how to resolve this error. I am trying to get an array of monthly transaction summation based on the summation of transactions occurring within each month.
here is my code below,
exports.monthlyTotalArray = async (req, res) => {
const { userId } = req.body;
const requestMonths = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
const today = new Date();
var relevantYear = today.getYear();
try {
return await Transaction.findAll({
where: {
userId: userId,
},
}).then((transactions) => {
if (transactions.length == 0) {
res.status(401).json({
message: { msgBody: "No transactions found", msgError: true },
});
} else {
const MonthlyArray = requestMonths.forEach((requestMonth) => {
transactions
.filter((i) => {
const date = new Date(i.date);
return (
date.getMonth() == requestMonth &&
date.getYear() == relevantYear
);
})
.reduce((prev, curr) => prev + parseFloat(curr.amount), 0);
res.status(200).send({
MonthlyArray,
});
});
}
});
} catch (error) {
res.status(500).send({
message: error.message || "some error occurred",
});
}
};
And I get this error when I try to run the code
{
"message": "Cannot access 'MonthlyArray' before initialization"
}

The main issue is that you're calling res.send({ MonthlyArray }) inside of the callback function for requestMonths.forEach, so that's why you're getting the error.
const MonthlyArray = requestMonths.forEach((requestMonth) => {
// you're calling it here
res.status(200).send({
MonthlyArray,
});
});
// instead, it should be here
res.status(200).send({
MonthlyArray,
});
Also, you probably don't want to use forEach because forEach doesn't return anything, so MonthlyArray will be undefined. Instead, use Array.map to get the result you need.
if (transactions.length == 0) {
// ...
} else {
const MonthlyArray = requestMonths.map((requestMonth) => {
return transactions
.filter((i) => {
const date = new Date(i.date);
return (
date.getMonth() == requestMonth && date.getYear() == relevantYear
);
})
.reduce((prev, curr) => prev + parseFloat(curr.amount), 0);
});
res.status(200).send({
MonthlyArray,
});
}

It looks like you are accessing MonthlyArray in the foreach() lambda expression. But MonthlyArray is actually initialized by the result of that foreach() so you are indeed accessing MonthlyArray before it gets initialized.
It's probably not what you intended but the part of code where you are sending the response is inside the lambda expression.
It's hard to see because the indentation of your code makes it difficult too understand where is the end of your lambda expression.
A proper indentation of your code would most likely make it obvious.

Related

onRequest vs onCall returning null

Please help me figure out the difference in return behaviour between the onCall and onRequest google functions below.
onCall, the problem: returns null on all returns, except at the first return (as commented below). The db entries and rest of the code works fine. Just no returns problem.
onRequest, returns perfectly fine on every return. The db entries and rest of the code also works fine.
Both as you will see compare the same, but I just can't seem to get it to work at all. Any advice on how to get my returns to work for the onCall (and structure it better) would be much appreciated.
I am keen on sticking with async await (as opposed to a promise). Using Node.js 12. I am calling the onCall in Flutter, don't know if that is relevant for the question.
The onCall:
exports.applyUserDiscount = functions.https.onCall(async (data, context) => {
if (!context.auth) return {message: "Authentication Required!", code: 401};
const uid = context.auth.uid;
const discountCode = data["discountCode"];
const cartTotal = data["cartTotal"];
try {
return await db.collection("discountCodes").where("itemID", "==", discountCode).limit(1).get()
.then(async (snapshot) => {
if (snapshot.empty) {
return "doesNotExist"; // The only return that works.
} else { // Everything else from here onwards returns null.
snapshot.forEach(async (doc) => {
if (doc.data().redeemed == true) {
return "codeUsed";
} else {
const newCartTotal = cartTotal - doc.data().discountAmount;
if (newCartTotal < 0) {
return "lessThanTotal";
} else {
doc.ref.update({
redeemed: true,
uid: uid,
redeemDate: fireDateTimeNow,
});
await db.collection("userdata").doc(uid).set({
cartDiscount: admin.firestore.FieldValue.increment(-doc.data().discountAmount),
}, {merge: true});
return doc.data().discountAmount.toString();
}
}
});
}
});
} catch (error) {
console.log("Error:" + error);
return "error";
}
});
The onRequest:
exports.applyUserDiscount = functions.https.onRequest(async (req, res) => {
const uid = req.body.uid;
const discountCode = req.body.discountCode;
const cartTotal = req.body.cartTotal;
try {
return await db.collection("discountCodes").where("itemID", "==", discountCode).limit(1).get()
.then(async (snapshot) => {
if (snapshot.isempty) {
res.send("doesNotExist");
} else {
snapshot.forEach(async (doc) => {
if (doc.data().redeemed == true) {
res.send("codeUsed");
} else {
const newCartTotal = cartTotal - doc.data().discountAmount;
if (newCartTotal < 0) {
res.send("lessThanTotal");
} else {
doc.ref.update({
redeemed: true,
uid: uid,
redeemDate: fireDateTimeNow,
});
await db.collection("userdata").doc(uid).set({
cartDiscount: admin.firestore.FieldValue.increment(-doc.data().discountAmount),
}, {merge: true});
res.send(doc.data().discountAmount.toString());
}
}
});
}
});
} catch (error) {
console.log(error);
res.send("error");
}
});
There are several points to be noted when looking at your code(s):
You should not use async/await within a forEach loop. The problem is that the callback passed to forEach() is not being awaited, see more explanations here or here. HOWEVER, in your case you don't need to loop over the QuerySnapshot since it contains only one doc. You can use the docs property which return an array of all the documents in the QuerySnapshot and take the first (and unique) element.
You mix-up then() with async/await, which is not recommended.
I would advise to throw exceptions for the "error" cases, like doesNotExist, codeUsed or lessThanTotal but it's up to you to choose. The fact that, for example, the lessThanTotal case is an error or a standard business case is debatable... So if you prefer to send a "text" response, I would advise to encapsulate this response in a Object with one property: in your front-end the response will always have the same format.
So, the following should do the trick. Note that I send back on object with a response element, including for the cases that could be considered as errors. As said above you could throw an exception in these cases.
exports.applyUserDiscount = functions.https.onCall(async (data, context) => {
if (!context.auth) ... //See https://firebase.google.com/docs/functions/callable#handle_errors
const uid = context.auth.uid;
const discountCode = data["discountCode"];
const cartTotal = data["cartTotal"];
try {
const snapshot = await db.collection("discountCodes").where("itemID", "==", discountCode).limit(1).get();
if (snapshot.empty) {
//See https://firebase.google.com/docs/functions/callable#handle_errors
} else {
const uniqueDoc = snapshot.docs[0];
if (uniqueDoc.data().redeemed == true) {
return { response: "codeUsed" };
} else {
const newCartTotal = cartTotal - uniqueDoc.data().discountAmount;
if (newCartTotal < 0) {
return { response: "lessThanTotal" };
} else {
await uniqueDoc.ref.update({ // See await here!!
redeemed: true,
uid: uid,
redeemDate: fireDateTimeNow,
});
await db.collection("userdata").doc(uid).set({
cartDiscount: admin.firestore.FieldValue.increment(-uniqueDoc.data().discountAmount),
}, { merge: true });
return {
response: uniqueDoc.data().discountAmount.toString()
}
}
}
}
} catch (error) {
console.log("Error:" + error);
return "error";
}
});

Nodejs Firebase Transaction - Maximum call stack size exceeded

I have a cloud function that uses a transaction to updates the players in a game. When the /players is null, i am trying to return a Map, but i get "Maximum call stack size exceeded".
Here is my cloud function:
export const addUserToGame = functions.https.onCall((data, context) => {
// Expected inputs - game_id(from data) and UID(from context)
if (context.auth == null) {
return {
"status": 403,
"message": "You are not authorized to access this feature"
};
}
const uid = context.auth.uid;
const game_id = data.game_id;
let gameIDRef = gamesRef.child(game_id);
return gameIDRef.once("value", function (snapshot) {
let players: Map<String, Number> = snapshot.child("players").val();
let max_players: Number = snapshot.child("max_players").val();
if (players != null && players.has(uid)) {
return {
"status": 403,
"message": "Player already in the game"
}
} else if (players != null && players.size >= max_players) {
return {
"status": 403,
"message": "Game is already full"
}
} else {
let playersNodeRef = gamesRef.child(game_id).child("players");
return playersNodeRef.transaction(t => {
if (t === null) {
return new Map<String, Number>().set(uid, 1);//trying to set a map with the player data, when the /players is null
} else {
let playersData: Map<String, Number> = t;
if (playersData.size >= max_players) { // rechecking
return;
} else {
playersData.set(uid, 1);
return playersData;
}
}
}).then(result => {
if (result.committed) { // if true there is a commit and the transaction went through
return {
"status": 200,
"message": "User added to game successfully"
}
} else {
return {
"status": 403,
"message": "Unable to add user at this time. Please try again"
}
}
}).catch(error => {
return {
"status": 403,
"message": error
}
});
}
});
});
Here is the stack trace:
addUserToGame
Function execution took 1423 ms, finished with status code: 500
at /workspace/node_modules/lodash/lodash.js:13401:38
at encode (/workspace/node_modules/firebase-functions/lib/providers/https.js:179:18)
at Function.mapValues (/workspace/node_modules/lodash/lodash.js:13400:7)
at baseForOwn (/workspace/node_modules/lodash/lodash.js:2990:24)
at /workspace/node_modules/lodash/lodash.js:4900:21
at keys (/workspace/node_modules/lodash/lodash.js:13307:14)
at isArrayLike (/workspace/node_modules/lodash/lodash.js:11333:58)
at isFunction (/workspace/node_modules/lodash/lodash.js:11653:17)
at baseGetTag (/workspace/node_modules/lodash/lodash.js:3067:51)
at Object (<anonymous>)
Unhandled error RangeError: Maximum call stack size exceeded
How can i set a map to /players node?
There were more than one issues with the code and as #Renaud pointed out, i have changed the 'once' callback to use promises version.
Also i had issues sending back data in the transaction. The data that i sent was using complex JS objects like Map(), but after some struggle (with the syntax) i changed it to a normal JS object (json like structure). Please see my changes below:
if (t === null) {
return [{ [uid]: { "status": 1 } }]; // if null, create an array and add an object to it
} else {
let playersData = t;
if (playersData.size >= max_players) { // rechecking
return;
} else { // if not null create an object and add to the existing array
playersData.push({
[uid]: {
"status": 1
}
});
return playersData;
}
}
Your problem most probably comes from the fact you are returning a complex JavaScript object, see https://stackoverflow.com/a/52569728/3371862.
In addition, note that you should use the promise version of the once() method, since, in a Callable Cloud Function you must return a promise that resolves with the data object to send back to the client.
Instead of doing
return gameIDRef.once("value", function (snapshot) {...});
do
return gameIDRef.once("value").then(snapshot => {...});
With this you will be able to correctly build the promise chain to be returned. Also, when dealing with the different cases around the players value, instead of returning JavaScript objects that will be handle in the .then((result) => {...}) block (which is not necessary and not really logical), throw errors that will be handled in the catch() block.
Something along the following lines:
export const addUserToGame = functions.https.onCall((data, context) => {
// Expected inputs - game_id(from data) and UID(from context)
if (context.auth == null) {
return {
status: 403,
message: 'You are not authorized to access this feature',
};
// IMHO better to do throw new functions.https.HttpsError('...', ...);
}
const uid = context.auth.uid;
const game_id = data.game_id;
let gameIDRef = gamesRef.child(game_id);
return gameIDRef
.once('value')
.then((snapshot) => {
let players: Map<String, Number> = snapshot.child('players').val();
let max_players: Number = snapshot.child('max_players').val();
if (players != null && players.has(uid)) {
throw new Error('Player already in the game');
} else if (players != null && players.size >= max_players) {
throw new Error('Game is already full');
} else {
let playersNodeRef = gamesRef.child(game_id).child('players');
return playersNodeRef.transaction((t) => {
if (t === null) {
return new Map<String, Number>().set(uid, 1); //trying to set a map with the player data, when the /players is null
} else {
let playersData: Map<String, Number> = t;
if (playersData.size >= max_players) {
// rechecking
return;
} else {
playersData.set(uid, 1);
return playersData;
}
}
});
}
})
.then((result) => {
if (result.committed) {
// if true there is a commit and the transaction went through
return {
status: 200,
message: 'User added to game successfully',
};
} else {
// probably throw an error here
return {
status: 403,
message: 'Unable to add user at this time. Please try again',
};
}
})
.catch((error) => {
if (error.message === 'Player already in the game') {
throw new functions.https.HttpsError('...', error.message);
} else if (error.message === 'Game is already full') {
throw new functions.https.HttpsError('...', error.message);
} else {
throw new functions.https.HttpsError('internal', error.message);
}
});
});
See here for more details on how to handle errors in a Callable Cloud Function.

How to delete element from array in object an return new object?

I have this object
let x = {
"name": "Ola",
"dates": [
{
"7.01.2020": [1, 2, 3]
},
{
"8.01.2020": [3 ,4]
}
],
"id": 7
}
and I need to be able to delete on button click chosen element from array. Ex. after button click, my object looks like this:
let x = {
"name": "Ola",
"dates": [
{
"7.01.2020": [1, 2]
},
{
"8.01.2020": [3 ,4]
}
],
"id": 7
}
Acrtually I've managed to filter this arr, the problem is when I try to return new x.dates arr. Please, notice that there are objects with different keys.
Any ideas? Thanks!
EDIT - whole func
try {
fetch(`http://localhost:3003/users/${userObject.id}`)
.then(res => res.json())
.then(user => {
const map = new Map(
user.dates.map((entry, i, arr) => {
const [key] = Object.keys(entry);
console.log(entry);
return [key, entry[key]];
})
);
result = map.get(date.toLocaleDateString());
console.log(user.dates);
return user;
})
.then(user => {
// Create array without deleted task
userDatesArr = result.filter(el => {
if (el !== itemText) {
return el;
}
});
result = userDatesArr;
// Update json-server - doesn't work, cause user.dates looks the same
patchFunc(user.dates);
});
} catch (err) {
console.error(err);
}
;
There are a few issues with that code:
You're assigning to a variable that isn't declared anywhere (result). That means the code is falling prey to what I call The Horror of Implicit Globals. You need to declare your variables. I strongly recommend turning on strict mode so the JavaScript engine will tell you when you do this.
That's now how you use filter, the callback should return a flag saying whether to keep the entry.
You're not checking for HTTP success. This is a footgun in the fetch API I write about here. You need to check ok on the response before calling json.
itemText is a string, but your array contains numbers. No string is ever === a number.
There is no reason for separating the code in your first then handler from the second, there's no intervening promise.
There's no reason to create the Map if you're not going to reuse it. You can just find the object once.
Nothing in the code handles errors the ajax call or the fulfillment handlers. (The try/catch you have wrapped around it will only catch errors calling fetch, not errors in the fetch operation of subsequent then handlers.)
Here's an updated version with some notes:
fetch(`http://localhost:3003/users/${userObject.id}`)
.then(res => {
// *** Check for HTTP success
if (!res.ok) {
throw new Error("HTTP error " + res.status);
}
return res.json();
})
.then(user => {
const targetValue = +itemText; // *** Convert to number
for (const entry of user.dates) {
const targetKey = Object.keys(entry)[0];
if (targetKey === key) {
// *** Remove the entry if present
entry[targetKey] = entry[targetKey].filter(value => value !== targetValue);
break;
}
}
// Update json-server
patchFunc(user.dates);
});
Note that that doesn't flag it up if the object for the target date isn't found. If you want to do that, you can add a flag:
fetch(`http://localhost:3003/users/${userObject.id}`)
.then(res => {
// *** Check for HTTP success
if (!res.ok) {
throw new Error("HTTP error " + res.status);
}
return res.json();
})
.then(user => {
const targetValue = +itemText; // *** Convert to number
let found = false;
for (const entry of user.dates) {
const targetKey = Object.keys(entry)[0];
if (targetKey === key) {
// *** Remove the entry if present
entry[targetKey] = entry[targetKey].filter(value => value !== targetValue);
found = true;
break;
}
}
if (!found) {
// *** Handle the fact it wasn't found
}
// Update json-server
patchFunc(user.dates);
});
You'll also want to add a rejection handler (.catch) to handle errors.
I think the below code snippet will give a better understanding of the problem.
Check the console, you will get the desired output. You need to do a deep cloning to avoid mutating the existing object (Deep Cloning - thanks to #nemisj)
let x = {
"name": "Ola",
"dates": [{
"7.01.2020": [1, 2, 3]
},
{
"8.01.2020": [3, 4]
}
],
"id": 7
}
function clone(item) {
if (!item) { return item; } // null, undefined values check
var types = [ Number, String, Boolean ],
result;
// normalizing primitives if someone did new String('aaa'), or new Number('444');
types.forEach(function(type) {
if (item instanceof type) {
result = type( item );
}
});
if (typeof result == "undefined") {
if (Object.prototype.toString.call( item ) === "[object Array]") {
result = [];
item.forEach(function(child, index, array) {
result[index] = clone( child );
});
} else if (typeof item == "object") {
// testing that this is DOM
if (item.nodeType && typeof item.cloneNode == "function") {
result = item.cloneNode( true );
} else if (!item.prototype) { // check that this is a literal
if (item instanceof Date) {
result = new Date(item);
} else {
// it is an object literal
result = {};
for (var i in item) {
result[i] = clone( item[i] );
}
}
} else {
// depending what you would like here,
// just keep the reference, or create new object
if (false && item.constructor) {
// would not advice to do that, reason? Read below
result = new item.constructor();
} else {
result = item;
}
}
} else {
result = item;
}
}
return result;
}
function deleteData (date, elementToBeDel) {
let newObj = clone(x) // spreading to avoid mutating the object
newObj.dates.map(dt => {
if(Object.keys(dt)[0] === date){
if(dt[date].indexOf(elementToBeDel) > -1){
dt[date].splice(dt[date].indexOf(elementToBeDel), 1);
}
}
})
console.log("old Object", x)
console.log("new Object",newObj)
}
<button onclick="deleteData('7.01.2020', 3)">Click Me to delete</button>

How to re-order Firestore 'modified' changes using DocumentChange.newIndex?

I'm using the web API for Firestore to perform a simple query ordered on a date property formatted as a string ('2017-12-30'). I use the onSnapshot() method to subscribe as a listener to document changes. The initial population of list of results works as expected - the order is correct.
As I make changes to the data, the callback then gets called with a change type of 'modified'. If any of the changes affects the date property, then I have no way of re-ordering the item in the list of results - unlike the old Realtime Database. That is, until I saw the newIndex and oldIndex properties of DocumentChange. They are undocumented for the Web API (https://firebase.google.com/docs/reference/js/firebase.firestore.DocumentChange), but are documented as part of the Node.js API (https://cloud.google.com/nodejs/docs/reference/firestore/0.10.x/DocumentChange).
So, my problem seemed to be solved - except that in practice the values in newIndex and oldIndex seem to be largely random and bear no relation to the actual order if I refresh the query. I can't make out any pattern that would explain the index values I get back.
Has anyone used DocumentChange.newIndex and DocumentChange.oldIndex successfully? If not, how would you reorder results in subscribers as a result of changes?
const query = firestore.collection(`users/${uid}/things`).
orderBy('sortDate', 'desc').limit(1000)
query.onSnapshot(snapshot => {
snapshot.docChanges.forEach(change => {
if (change.type === "added") {
dispatch(addThing({
id: change.doc.id,
...change.doc.data()
}, change.newIndex)
}
if (change.type === "modified") {
dispatch(changeThing({
id: change.doc.id,
...change.doc.data()
}, change.oldIndex, change.newIndex))
}
if (change.type === "removed") {
dispatch(removeThing(change.doc.id, change.oldIndex))
}
})
})
The original problem I had with the DocumentChange indexes was due to a couple of bugs elsewhere in my code. As I didn't find any examples of this in use outside of the Node.js Firestore docs, here's the test code I used to verify its correct behaviour (ES6). It assumes firebase has been initialized.
cleanTestData = (firestore, path) => {
console.log("Cleaning-up old test data")
var query = firestore.collection(path)
return query.get().then(snapshot => {
const deletePromises = []
if (snapshot.size > 0) {
snapshot.docs.forEach(function(doc) {
deletePromises.push(doc.ref.delete().then(() => {
console.log("Deleted ", doc.id)
}))
});
}
return Promise.all(deletePromises)
}).then(() => {
console.log("Old test data cleaned-up")
})
}
createTestData = (firestore, path) => {
console.log("Creating test data")
const batch = firestore.batch()
const data = {
a: '2017-09-02',
b: '2017-12-25',
c: '2017-10-06',
d: '2017-08-02',
e: '2017-09-20',
f: '2017-11-17'
}
for (const id in data) {
batch.set(firestore.collection(path).doc(id), { date: data[id] })
}
return batch.commit().then(() => {
console.log("Test data created");
}).catch(error => {
console.error("Failed to create test data: ", error);
})
}
subscribe = (firestore, path) => {
const datesArray = []
return firestore.collection(path).orderBy('date', 'asc').onSnapshot(snapshot => {
snapshot.docChanges.forEach(change => {
console.log(change.type, "id:", change.doc.id,
"; date:", change.doc.data().date,
"; oldIndex:", change.oldIndex, "; newIndex:", change.newIndex,
"; metadata: ", change.doc.metadata)
if (change.oldIndex !== -1) {
datesArray.splice(change.oldIndex, 1);
}
if (change.newIndex !== -1) {
datesArray.splice(change.newIndex, 0, change.doc.data().date);
}
console.log(" -->", JSON.stringify(datesArray))
})
})
}
update = (firestore, path) => {
console.log("Updating test data")
return firestore.collection(path).doc('d').set({date: '2018-01-02'}).then(() => {
console.log("Test doc 'd' updated from '2017-08-02' to '2018-01-02'")
})
}
query = (firestore, path) => {
var query = firestore.collection(path).orderBy('date', 'asc')
return query.get().then(snapshot => {
const dates = []
if (snapshot.size > 0) {
snapshot.docs.forEach(function(doc) {
dates.push(doc.data().date)
});
}
console.log("Fresh query of data: \n -->", JSON.stringify(dates))
})
}
handleStartTest = e => {
console.log("Starting test")
const firestore = firebase.firestore()
const path = `things`
let unsubscribeFn = null
unsubscribeFn = this.subscribe(firestore, path)
this.cleanTestData(firestore, path).then(() => {
return this.createTestData(firestore, path)
}).then(() => {
return this.update(firestore, path)
}).then(() => {
return this.query(firestore, path)
}).then(() => {
unsubscribeFn()
console.log("Test complete")
}).catch((error) => {
console.error("Test failed: ", error)
})
}
This is the way it worked for me:
onSnapshot((ref) => {
ref.docChanges().forEach((change) => {
const { newIndex, oldIndex, doc, type } = change;
if (type === 'added') {
this.todos.splice(newIndex, 0, doc.data());
// if we want to handle references we would do it here
} else if (type === 'modified') {
// remove the old one first
this.todos.splice(oldIndex, 1);
// if we want to handle references we would have to unsubscribe
// from old references' listeners and subscribe to the new ones
this.todos.splice(newIndex, 0, doc.data());
} else if (type === 'removed') {
this.todos.splice(oldIndex, 1);
// if we want to handle references we need to unsubscribe
// from old references
}
});
});
source

The right solution for promises

I have the app on node.js with connecting to firebase. I need to update the data correctly.
How to call the function getOrSetUserTrackDay(day) in a promise to get a good value, but not undefined?
let userData = [];
let userID = req.params.userID;
let today = req.params.today;
let yesterday = req.params.yesterday;
db.collection('users').doc(userID).get()
.then((userDataFromDB) => {
if (!userDataFromDB.exists) {
res.status(404).send('User not found');
}
else {
function getOrSetUserTrackDay(day) {
let userTrackRef = db.collection('history').doc('daily').collection(day).doc(userID);
userTrackRef.get()
.then(userTrackData => {
if (userTrackData.exists) {
return userTrackData.data(); // good
}
else {
let userNewData = {
username: userDataFromDB.data().username,
photoUrl: userDataFromDB.data().photoUrl
};
userTrackRef.update(userNewData);
return userNewData; // good
}
})
}
userData = {
user: userDataFromDB.data(),
today: getOrSetUserTrackDay(today), // undefined
yesterday: getOrSetUserTrackDay(yesterday) // undefined
};
res.json(userData);
}
})
.catch((err) => {
console.log(err);
res.status(404).send(err);
});
well getOrSetUserTrackDay has no return statement, hence it returns undefined - but, since it contains asynchronous code, you'll never be able to use it synchronously
So, you can do the following
let userData = [];
let userID = req.params.userID;
let today = req.params.today;
let yesterday = req.params.yesterday;
db.collection('users').doc(userID).get()
.then((userDataFromDB) => {
if (!userDataFromDB.exists) {
res.status(404).send('User not found');
}
else {
let getOrSetUserTrackDay = day => {
let userTrackRef = db.collection('history').doc('daily').collection(day).doc(userID);
return userTrackRef.get()
.then(userTrackData => {
if (userTrackData.exists) {
return userTrackData.data(); // good
} else {
let userNewData = {
username: userDataFromDB.data().username,
photoUrl: userDataFromDB.data().photoUrl
};
userTrackRef.update(userNewData);
return userNewData; // good
}
});
};
Promise.all([getOrSetUserTrackDay(today), getOrSetUserTrackDay(yesterday)])
.then(([today, yesterday]) => res.json({
user: userDataFromDB.data(),
today,
yesterday
}));
}
}).catch((err) => {
console.log(err);
res.status(404).send(err);
});
Note: changed getOrSetUserTrackDay from a function declaration to a function expression (in this case, an arrow function for no particular reason) - because Function declarations should not be placed in blocks. Use a function expression or move the statement to the top of the outer function.

Categories