Stripe functions returning calls after function finished - javascript

Relatively new to Javascript however, i'm trying to work with Stripe and my way around a user submitting another payment method and then paying an invoice with that method. if the payment fails again - it should remove the subscription alltogether. I'm using Firebase Realtime Database with GCF & Node.js 8.
Here is what i have so far
exports.onSecondPaymentAttempt = functions.database.ref("users/{userId}/something/somethingHistory/{subDbId}/newPayment").onCreate((snapshot, context)=>{
var s = snapshot.val();
var fields = s.split(",");
const cardToken = fields[0];
const cus_id = fields[1];
const conn_id = fields[2];
const subDbId = context.params.subDbId;
const userId = context.params.userId;
return stripe.customers.createSource(
cus_id,
{source: cardToken},{
stripeAccount: `${conn_id}`,
},
(err, card)=> {
console.log(err);
if(err){
return console.log("error attaching card "+ err)
}else{
const invoiceNo = admin.database().ref(`users/${userId}/something/somethingHistory/${subDbId}`)
return invoiceNo.once('value').then(snapshot=>{
const invoiceNumber = snapshot.child("invoiceId").val();
const subId = snapshot.child("subscriptionId").val();
return stripe.invoices.pay(
invoiceNumber,
{
expand: ['payment_intent','charge','subscription'],
},{
stripeAccount: `${conn_id}`,
},
(err, invoice)=>{
if(err){
return console.log("error paying invoice "+ err)
}else{
if(invoice.payment_intent.status==="succeeded"){
//DO SOME CODE
return console.log("New Payment succeeded for "+invoiceNumber)
}else{
//DO SOME OTHER CODE
//CANCEL
return stripe.subscriptions.del(
subId,{
stripeAccount: `${conn_id}`,
},
(err, confirmation)=> {
if(err){
return console.log("Subscription error")
}else{
return console.log("Subscription cancelled")
}
});
}
}
});
})
}
});
To me it looks like an incredibly inefficient / ugly way of achieving the effect and overall the user is sitting waiting for a response for approx 15 seconds although the function finishes its execution after 1862ms - I still get responses up to 5 - 10 seconds after.
What's the most efficient way of achieving the same desired effect of registering a new payment source, paying subscription and then handling the result of that payment?

You should use the Promises returned by the Stripe asynchronous methods, as follows (untested, it probably needs some fine tuning, in particular with the objects passed to the Stripe methods):
exports.onSecondPaymentAttempt = functions.database.ref("users/{userId}/something/somethingHistory/{subDbId}/newPayment").onCreate((snapshot, context) => {
var s = snapshot.val();
var fields = s.split(",");
const cardToken = fields[0];
const cus_id = fields[1];
const conn_id = fields[2];
const subDbId = context.params.subDbId;
const userId = context.params.userId;
return stripe.customers.createSource(
//Format of this object to be confirmed....
cus_id,
{ source: cardToken },
{ stripeAccount: `${conn_id}` }
)
.then(card => {
const invoiceNo = admin.database().ref(`users/${userId}/something/somethingHistory/${subDbId}`)
return invoiceNo.once('value')
})
.then(snapshot => {
const invoiceNumber = snapshot.child("invoiceId").val();
const subId = snapshot.child("subscriptionId").val();
return stripe.invoices.pay(
invoiceNumber,
{ expand: ['payment_intent', 'charge', 'subscription'] },
{ stripeAccount: `${conn_id}` }
)
})
.then(invoice => {
if (invoice.payment_intent.status === "succeeded") {
//DO SOME CODE
console.log("New Payment succeeded for " + invoiceNumber)
return null;
} else {
//DO SOME OTHER CODE
//CANCEL
return stripe.subscriptions.del(
subId, {
stripeAccount: `${conn_id}`,
});
}
})
.catch(err => {
//....
return null;
})
});
I would suggest you watch the three videos about "JavaScript Promises" from the official Firebase video series, which explain why it is key to correctly chain and return the promises returned by the asynchronous methods.

Related

Node js promise chaining issue

I am trying to connect to Salesforce using node js / jsforce library and use promises. Unfortunately one of the methods is executing prior to getting connection.
i have method A : makeconnection which returns the connection
i have method B : which loads data from Salesforce based on the connection reference from method A
I have method C : which gets dependencies from Salesforce based on connection from method A
I would like the following order to be executed A ==> B ==> C
Unfortunately C seems to run first followed by A and B so the connection is null and it fails
roughly this is the code
let jsforce = require("jsforce");
const sfdcSoup = require("sfdc-soup");
const fs = require("fs");
let _ = require("lodash");
let trgarr = [];
let clsarr = [];
let entityarr = [];
function makeConnection() {
return new Promise((resolve,reject) => {
const conn = new jsforce.Connection({
loginUrl: "https://test.salesforce.com",
instanceUrl: "salesforce.com",
serverUrl: "xxx",
version: "50.0"
});
conn.login(username, password, function (err, userInfo) {
if (err) {
return console.error(err);
}
// console.log(conn.accessToken);
//console.log(conn.instanceUrl);
//console.log("User ID: " + userInfo.id);
//console.log("Org ID: " + userInfo.organizationId);
console.log("logged in");
});
resolve(conn);
});
}
function loadClasses(conn) {
return new Promise((resolve,reject) => {
const querystr =
"select apiVersion,name,body from apexClass where NamespacePrefix = null";
let query = conn
.query(querystr)
.on("record", function (rec) {
clsarr.push(rec);
})
.on("end", function () {
console.log("number of class is " + clsarr.length);
console.log("loaded all classes");
});
resolve(conn,clsarr);
});
}
async function getDependencies(conn) {
return new Promise((resolve,reject) => {
let entryPoint = {
name: "xxx",
type: "CustomField",
id: yyy
};
let connection = {
token: conn.accessToken,
url: "abc.com",
apiVersion: "50.0"
};
let usageApi = sfdcSoup.usageApi(connection, entryPoint);
usageApi.getUsage().then((response) => {
console.log(response.stats);
console.log(response.csv);
});
});
}
async function run() {
makeConnection().then(conn => loadClasses(conn)).then(conn=>getDependencies(conn));
}
run();
I keep getting an error that says UnhandledPromiseRejectionWarning: Error: Access token and URL are required on the connection object
The reason is connection needs to be obtained from method A and sent to Method C , which is not happening. Can you please guide where i might be wrong?
Also why is method C getting executed before A and B. why does my promise chaining not work as promised?
I am running the code in Vscode and using Node 14
Your 2 method have minor correction first method makeConnection, the resolve should be inside login after console.log("logged in")
And second loadClasses, the resolve should be inside 'end' event. Please check below 2 method.
function makeConnection() {
return new Promise((resolve,reject) => {
const conn = new jsforce.Connection({
loginUrl: "https://test.salesforce.com",
instanceUrl: "salesforce.com",
serverUrl: "xxx",
version: "50.0"
});
conn.login(username, password, function (err, userInfo) {
if (err) {
return console.error(err);
}
// console.log(conn.accessToken);
//console.log(conn.instanceUrl);
//console.log("User ID: " + userInfo.id);
//console.log("Org ID: " + userInfo.organizationId);
console.log("logged in");
resolve(conn);
});
});
}
function loadClasses(conn) {
return new Promise((resolve,reject) => {
const querystr =
"select apiVersion,name,body from apexClass where NamespacePrefix = null";
let query = conn
.query(querystr)
.on("record", function (rec) {
clsarr.push(rec);
})
.on("end", function () {
console.log("number of class is " + clsarr.length);
console.log("loaded all classes");
resolve(conn,clsarr);
});
});
}
you should use promise series if methods are depending on each other if methods do not depend then you should use a promise parallel.
READ MORE ABOUT PROMISE SERIES AND PARALLEL.

Having a promise issue with my google cloud function

I have an http trigger in cloud functions that is working, however I am getting some logs come back in a weird order. I am pretty sure I have not executed the promises correctly.
here is the code:
exports.createGame = functions.https.onRequest((req, res) => {
return cors(req, res, () => {
// Check for POST request
if (req.method !== "POST") {
res.status(400).send('Please send a POST request');
return;
}
const createGame = admin.firestore().collection('games')
generatePin()
function generatePin() {
...pin generating code goes here...
addGame(newGidToString)
}
function addGame(newGidToString) {
var getGame = admin.firestore().collection('games')
getGame = getGame.where('gid', '==', newGidToString)
getGame.get().then(snapshot => {
if (snapshot.empty) {
console.log('BODY ', req.body)
const promises = [];
const gameTitle = req.body.gametitle
const targetGID = req.body.target
const numPlayers = req.body.playercount.toString()
const newGID = newGidToString
const collections = ['games', 'clues', 'detours', 'messages', 'questions', 'tagstate']
collections.forEach(function (element) {
console.log('start loop', element)
let elementsRef = admin.firestore().collection(element)
elementsRef = elementsRef.where('gid', '==', targetGID)
elementsRef.get().then(snapshot => {
var batch = admin.firestore().batch()
snapshot.forEach(function (doc) {
// change the gid to the new game gid
let data = doc.data()
data.gid = newGID
if(data.template){
data.template = false
}
if(!data.parent) {
data.parent = targetGID
}
if (!data.playercount) {
data.playercount = numPlayers
}
if (data.gametitle) {
data.gametitle = gameTitle
}
let elementRef = admin.firestore().collection(element).doc()
batch.set(elementRef, data)
})
batch.commit().then(data => {
console.log('complete batch ', element)
})
})
})
res.send({ status: 200, message: 'Game: added.', pin: newGID})
console.log('completed');
} else {
console.log('found a match ', snapshot)
// re-run the generator for a new pin
generatePin()
}
})
}
})
})
Here is a screen shot of the console logs.
Notice the order of the logs. It would seem logical that completed would come at the end fo the loops.
Any help is great appreciated.
UPDATE:
function addGame(newGidToString) {
var getGame = admin.firestore().collection('games')
getGame = getGame.where('gid', '==', newGidToString)
getGame.get().then(snapshot => {
if (snapshot.empty) {
console.log('BODY ', req.body)
const promises = [];
const gameTitle = req.body.gametitle
const targetGID = req.body.target
const numPlayers = req.body.playercount.toString()
const newGID = newGidToString
const collections = ['games', 'clues', 'detours', 'messages', 'questions', 'tagstate']
collections.forEach(function (element) {
console.log('start loop', element)
let elementsRef = admin.firestore().collection(element)
elementsRef = elementsRef.where('gid', '==', targetGID)
// elementsRef.get().then(snapshot => {
const promGet = elementsRef.get(); //lets get our promise
promises.push(promGet); //place into an array
promGet.then(snapshot => { //we can now continue as before
var batch = admin.firestore().batch()
snapshot.forEach(function (doc) {
// change the gid to the new game gid
let data = doc.data()
data.gid = newGID
if(data.template){
data.template = false
}
if(!data.parent) {
data.parent = targetGID
}
if (!data.playercount) {
data.playercount = numPlayers
}
if (data.gametitle) {
data.gametitle = gameTitle
}
let elementRef = admin.firestore().collection(element).doc()
batch.set(elementRef, data)
})
batch.commit().then(data => {
console.log('complete batch ', element)
})
})
})
Promise.all(promises).then(() => {
res.send({ status: 200, message: 'Game: added.', pin: newGID })
console.log('completed');
});
// res.send({ status: 200, message: 'Game: added.', pin: newGID})
// console.log('completed');
} else {
console.log('found a match ', snapshot)
// re-run the generator for a new pin
generatePin()
}
})
}
SECOND UPDATE: (based off Keith's answer)
function addGame(newGidToString) {
var getGame = admin.firestore().collection('games')
getGame = getGame.where('gid', '==', newGidToString)
getGame.get().then(snapshot => {
if (snapshot.empty) {
console.log('BODY ', req.body)
const gameTitle = req.body.gametitle
const targetGID = req.body.target
const numPlayers = req.body.playercount.toString()
const newGID = newGidToString
const promises = [];
const collections = ['games', 'clues', 'detours', 'messages', 'questions', 'tagstate']
collections.forEach(function (element) {
console.log('start loop', element)
let elementsRef = admin.firestore().collection(element)
elementsRef = elementsRef.where('gid', '==', targetGID)
const getPromise = elementsRef.get(); //lets get our promise
promises.push(getPromise); //place into an array
getPromise.then(snapshot => { //we can now continue as before
var batch = admin.firestore().batch()
snapshot.forEach(function (doc) {
// change the gid to the new game gid
let data = doc.data()
data.gid = newGID
if(data.template){
data.template = false
}
if(!data.parent) {
data.parent = targetGID
}
if (!data.playercount) {
data.playercount = numPlayers
}
if (data.gametitle) {
data.gametitle = gameTitle
}
let elementRef = admin.firestore().collection(element).doc()
batch.set(elementRef, data)
})
batch.commit()
})
})
Promise.all(promises).then(() => {
res.send({ status: 200, message: 'Game: added.', pin: newGID })
console.log('completed from promise');
});
} else {
console.log('found a match ', snapshot)
// re-run the generator for a new pin
generatePin()
}
})
}
When looping with promises, one thing to remember is if you do a forEach, it's not going to wait.
It appears that what the OP wants to do is before he hits the complete, and does a res send, wants to make sure all promises have been completed.
Promise.all https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all , is exactly what this does.
So every time you create a promise if you push this onto an array, we can later use Promise.all to make sure all the promises are complete.
So if we replace
elementsRef.get().then(snapshot => {
with
const promGet = elementsRef.get(); //lets get our promise
promises.push(promGet); //place into an array
promGet.then(snapshot => { //we can now continue as before
Your promise is now stored for later so that Promise.all can check to make sure everything has complete. You could do this in one line, but I feel it's easier to see what's happening this way. eg->
promises.push(promGet.then(snapshot => {
Finally now we have got our promise array, before we do our res.send, lets make sure they have all completed.
Promise.all(promises).then(() => {
res.send({ status: 200, message: 'Game: added.', pin: newGID})
console.log('completed');
});
One thing to keep in mind when using Promises this way, is that you have a fair few things going on at the same time, sometimes this can be an issue, eg. connected to some service might put restrictions on the number of concurrent connections etc. So in these cases it might be an idea doing things in series or a mix of both. There are even libs that can do concurrecy, eg. Do only X amounts of promises at a time.
And finally, if you can use async / await, I'd advice using them, as it makes Promises so much nicer to work with.

Nested HTTP requests in Firebase cloud function

I'm using an HTTP-triggered Firebase cloud function to make an HTTP request. I get back an array of results (events from Meetup.com), and I push each result to the Firebase realtime database. But for each result, I also need to make another HTTP request for one additional piece of information (the category of the group hosting the event) to fold into the data I'm pushing to the database for that event. Those nested requests cause the cloud function to crash with an error that I can't make sense of.
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();
const request = require('request');
exports.foo = functions.https.onRequest(
(req, res) => {
var ref = admin.database().ref("/foo");
var options = {
url: "https://api.meetup.com/2/open_events?sign=true&photo-host=public&lat=39.747988&lon=-104.994945&page=20&key=****",
json: true
};
return request(
options,
(error, response, body) => {
if (error) {
console.log(JSON.stringify(error));
return res.status(500).end();
}
if ("results" in body) {
for (var i = 0; i < body.results.length; i++) {
var result = body.results[i];
if ("name" in result &&
"description" in result &&
"group" in result &&
"urlname" in result.group
) {
var groupOptions = {
url: "https://api.meetup.com/" + result.group.urlname + "?sign=true&photo-host=public&key=****",
json: true
};
var categoryResult = request(
groupOptions,
(groupError, groupResponse, groupBody) => {
if (groupError) {
console.log(JSON.stringify(error));
return null;
}
if ("category" in groupBody &&
"name" in groupBody.category
) {
return groupBody.category.name;
}
return null;
}
);
if (categoryResult) {
var event = {
name: result.name,
description: result.description,
category: categoryResult
};
ref.push(event);
}
}
}
return res.status(200).send("processed events");
} else {
return res.status(500).end();
}
}
);
}
);
The function crashes, log says:
Error: Reference.push failed: first argument contains a function in property 'foo.category.domain._events.error' with contents = function (err) {
if (functionExecutionFinished) {
logDebug('Ignoring exception from a finished function');
} else {
functionExecutionFinished = true;
logAndSendError(err, res);
}
}
at validateFirebaseData (/user_code/node_modules/firebase-admin/node_modules/#firebase/database/dist/index.node.cjs.js:1436:15)
at /user_code/node_modules/firebase-admin/node_modules/#firebase/database/dist/index.node.cjs.js:1479:13
at Object.forEach (/user_code/node_modules/firebase-admin/node_modules/#firebase/util/dist/index.node.cjs.js:837:13)
at validateFirebaseData (/user_code/node_modules/firebase-admin/node_modules/#firebase/database/dist/index.node.cjs.js:1462:14)
at /user_code/node_modules/firebase-admin/node_modules/#firebase/database/dist/index.node.cjs.js:1479:13
at Object.forEach (/user_code/node_modules/firebase-admin/node_modules/#firebase/util/dist/index.node.cjs.js:837:13)
at validateFirebaseData (/user_code/node_modules/firebase-admin/node_modules/#firebase/database/dist/index.node.cjs.js:1462:14)
at /user_code/node_modules/firebase-admin/node_modules/#firebase/database/dist/index.node.cjs.js:1479:13
at Object.forEach (/user_code/node_modules/firebase-admin/node_modules/#firebase/util/dist/index.node.cjs.js:837:13)
at validateFirebaseData (/user_code/node_modules/firebase-admin/node_modules/#firebase/database/dist/index.node.cjs.js:1462:14)
If I leave out the bit for getting the group category, the rest of the code works fine (just writing the name and description for each event to the database, no nested requests). So what's the right way to do this?
I suspect this issue is due to the callbacks. When you use firebase functions, the exported function should wait on everything to execute or return a promise that resolves once everything completes executing. In this case, the exported function will return before the rest of the execution completes.
Here's a start of something more promise based -
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();
const request = require("request-promise-native");
exports.foo = functions.https.onRequest(async (req, res) => {
const ref = admin.database().ref("/foo");
try {
const reqEventOptions = {
url:
"https://api.meetup.com/2/open_events?sign=true&photo-host=public&lat=39.747988&lon=-104.994945&page=20&key=xxxxxx",
json: true
};
const bodyEventRequest = await request(reqEventOptions);
if (!bodyEventRequest.results) {
return res.status(200).end();
}
await Promise.all(
bodyEventRequest.results.map(async result => {
if (
result.name &&
result.description &&
result.group &&
result.group.urlname
) {
const event = {
name: result.name,
description: result.description
};
// get group information
const groupOptions = {
url:
"https://api.meetup.com/" +
result.group.urlname +
"?sign=true&photo-host=public&key=xxxxxx",
json: true
};
const categoryResultResponse = await request(groupOptions);
if (
categoryResultResponse.category &&
categoryResultResponse.category.name
) {
event.category = categoryResultResponse.category.name;
}
// save to the databse
return ref.push(event);
}
})
);
return res.status(200).send("processed events");
} catch (error) {
console.error(error.message);
}
});
A quick overview of the changes -
Use await and async calls to wait for things to complete vs. being triggered in a callback (async and await are generally much easier to read than promises with .then functions as the execution order is the order of the code)
Used request-promise-native which supports promises / await (i.e. the await means wait until the promise returns so we need something that returns a promise)
Used const and let vs. var for variables; this improves the scope of variables
Instead of doing checks like if(is good) { do good things } use a if(isbad) { return some error} do good thin. This makes the code easier to read and prevents lots of nested ifs where you don't know where they end
Use a Promise.all() so retrieving the categories for each event is done in parallel
There are two main changes you should implement in your code:
Since request does not return a promise you need to use an interface wrapper for request, like request-promise in order to correctly chain the different asynchronous events (See Doug's comment to your question)
Since you will then call several times (in parallel) the different endpoints with request-promise you need to use Promise.all() in order to wait all the promises resolve before sending back the response. This is also the case for the different calls to the Firebase push() method.
Therefore, modifying your code along the following lines should work.
I let you modifying it in such a way you get the values of name and description used to construct the event object. The order of the items in the results array is exactly the same than the one of the promises one. So you should be able, knowing that, to get the values of name and description within results.forEach(groupBody => {}) e.g. by saving these values in a global array.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
var rp = require('request-promise');
exports.foo = functions.https.onRequest((req, res) => {
var ref = admin.database().ref('/foo');
var options = {
url:
'https://api.meetup.com/2/open_events?sign=true&photo-host=public&lat=39.747988&lon=-104.994945&page=20&key=****',
json: true
};
rp(options)
.then(body => {
if ('results' in body) {
const promises = [];
for (var i = 0; i < body.results.length; i++) {
var result = body.results[i];
if (
'name' in result &&
'description' in result &&
'group' in result &&
'urlname' in result.group
) {
var groupOptions = {
url:
'https://api.meetup.com/' +
result.group.urlname +
'?sign=true&photo-host=public&key=****',
json: true
};
promises.push(rp(groupOptions));
}
}
return Promise.all(promises);
} else {
throw new Error('err xxxx');
}
})
.then(results => {
const promises = [];
results.forEach(groupBody => {
if ('category' in groupBody && 'name' in groupBody.category) {
var event = {
name: '....',
description: '...',
category: groupBody.category.name
};
promises.push(ref.push(event));
} else {
throw new Error('err xxxx');
}
});
return Promise.all(promises);
})
.then(() => {
res.send('processed events');
})
.catch(error => {
res.status(500).send(error);
});
});
I made some changes and got it working with Node 8. I added this to my package.json:
"engines": {
"node": "8"
}
And this is what the code looks like now, based on R. Wright's answer and some Firebase cloud function sample code.
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();
const request = require("request-promise-native");
exports.foo = functions.https.onRequest(
async (req, res) => {
var ref = admin.database().ref("/foo");
var options = {
url: "https://api.meetup.com/2/open_events?sign=true&photo-host=public&lat=39.747988&lon=-104.994945&page=20&key=****",
json: true
};
await request(
options,
async (error, response, body) => {
if (error) {
console.error(JSON.stringify(error));
res.status(500).end();
} else if ("results" in body) {
for (var i = 0; i < body.results.length; i++) {
var result = body.results[i];
if ("name" in result &&
"description" in result &&
"group" in result &&
"urlname" in result.group
) {
var groupOptions = {
url: "https://api.meetup.com/" + result.group.urlname + "?sign=true&photo-host=public&key=****",
json: true
};
var groupBody = await request(groupOptions);
if ("category" in groupBody && "name" in groupBody.category) {
var event = {
name: result.name,
description: result.description,
category: groupBody.category.name
};
await ref.push(event);
}
}
}
res.status(200).send("processed events");
}
}
);
}
);

Removing Percent Encoding from Firebase Cloud Functions

The firebase function I'm currently using retrieves data from a certain branch in the database where the value may or may not have percent encoding. The value is a user's username and it's encoded if there's a '.' in the name. When the user gets a notification, it has their name in the body of it, and I'm trying to figure out how to removePercentEncoding if necessary. My cloud function:
exports.newPost = functions.database.ref('/{school}/posts').onWrite((change, context) => {
const school = context.params.school
const postUsername = admin.database().ref('/{school}/lastPost/lastPostUser').once('value')
var db = admin.database();
var val1, val2;
db.ref(`/Herrick Middle School/lastPost/lastPostUser`).once('value').then(snap => {
val1 = snap.val();
console.log(snap.val());
return val1
}).then(() => {
return db.ref("test2/val").once('value');
}).catch(err => {
console.log(err);
});
return loadUsers().then(users => {
let tokens = [];
for (let user of users) {
tokens.push(user.pushToken);
console.log(`pushToken: ${user.pushToken}`);
}
let payload = {
notification: {
title: school,
body: `${val1} just posted something.`,
sound: 'Apex',
badge: '1'
}
};
return admin.messaging().sendToDevice(tokens, payload);
});
});
function loadUsers() {
let dbRef = admin.database().ref('/Herrick Middle School/regisTokens');
let defer = new Promise((resolve, reject) => {
dbRef.once('value', (snap) => {
let data = snap.val();
let users = [];
for (var property in data) {
users.push(data[property]);
console.log(`data: ${property}`);
}
resolve(users);
}, (err) => {
reject(err);
});
});
return defer;
}
More specifically, I was hoping someone could shed some light on how to remove encoding from
val
Thanks in advance.
not sure i understand but either native JS decodeURI() or regex like this
var encoded = "john%doe%doe%bird";
console.log(encoded.replace(/%/g, "."));

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