I try the response model after create data but it doesn't work. It shows data on "console.log" and it didn't respond when I use "resolve({})".
In the routers.js:
const register = require('./functions/register');
module.exports = router => {
router.get('/', (req, res) => res.end('Welcome to Idol Fan With your Idol !'));
//======REGISTER & LOGIN WITH SOCIAL =====
router.post('/socialuser', (req, res) => {
const social_id = req.body.social_id;
const token = req.body.token;
const name = req.body.name;
const email = req.body.email;
const photoprofile = req.body.photoprofile;
const tokenfirebase = req.body.tokenfirebase;
if (!social_id) {
res.status(400).json({message: 'Invalid Request !'});
} else {
register.registerUser(social_id, name, email, photoprofile, token, tokenfirebase)
.then(result => {
res.status(result.status).json({status: result.status, message: result.message, user: result.user})
})
.catch(err => res.status(err.status).json({message: err.message}));
}
});
}
Function Register.js:
const userfan = require('../models/user');
exports.registerUser = (social_id, name, email, photoprofile, token,
tokenfirebase) =>
new Promise((resolve, reject) => {
const d = new Date();
const timeStamp = d.getTime();
userfan.find({social_id: social_id})
.then(users => {
if (users.length == 0) {
let newUser = new userfan({
social_id: social_id,
name: name,
email: email,
photoprofile: photoprofile,
token: token,
tokenfirebase: tokenfirebase,
created_at: timeStamp
});
newUser.save()
.then(doc => {
console.log("run... " + doc);
resolve({
status: 200,
message: 'User Register Sucessfully !',
user: doc
});
})
.catch(err => {
console.error(err)
if (err.code == 11000) {
reject({status: 409, message: 'User Already Registered !'});
} else {
reject({status: 500, message: 'Internal Server Error !'});
}
});
} else {
return users[0];
}
})
.then(usertemp => resolve({status: 200, message: "Login Successfully !", user: usertemp}))
.catch(err => {
console.log(err.message);
reject({status: 500, message: err.message});
});
});
This is my result after run on server:
As a result and code above. I have a question Why "user: doc" no response?. Thank you so much!
userfan.find.then.then (synchronous since nothing requires waiting) is called before newUser.save.then (asynchronous since under the hood it waits for the DB to answer).
So both resolve are called, but only the first call is considered, and the first one to be called is the one using usertemp. And this one receives undefined as argument, because of the implicit return undefined at the end of userfan.find.then.
Your flow should be:
userfan.find().then(users => {
if (!users.length) {
let newUser = ...
// explicitly return the promise, which will give the created user
return newUser.save().then(doc => {
// format and return the value you want
return {user: doc, message: 'registered'};
});
} else {
// another explicitly returned value, but you already have this
return {user: users[0], message: 'login'};
}
// it's not possible here because we returned before it, but your code reached this point
// and, implicitly, any function ending with no return does:
return undefined;
// this .then receives *either* the promise from newUser.save *or* the object from the else block
}).then(structure => {
structure.status = 200;
return structure; // here structure is {status: 200, message: 'registered'|'login', user: document}
});
Also, note that using the syntax shortcut (without curly braces around the function body) for arrow functions implies the one-line body is returned:
singleArgument => doSomething();
// is actually *strictly* equivalent to
(singleArgument) => { return doSomething(); }
With these ways of writing, it's easy to lose the reflex of writing return when it's needed.
Related
What is the best way to chain axios / firebase promises that must be linked in a specific order and use the returns of previous promises?
I am writing a firebase function that allows me to update a user via a third-party JWT API. So I have to fulfill several promises (I use axios for that) to build the final query with a uid, a token and a refresh token.
These requests must be executed in the right order, each promise waiting for the result of the previous one to be able to execute.
recover the firebase client token to identify the user
search in a collection for the tokens (access & refresh) that were previously stored and associated with the user's uid.
Execute the "me" request on the third-party API to retrieve the user's information and update the user.
My question: What is the most correct way to chase these axios promises?
For the moment, I have managed to achieve this result, by interlocking the calls successively to properly manage the "catch" and by moving in separate functions the calls to make a little more digest the reading of the code.
/* index.js */
const userModule = require('./user');
exports.me = functions.https.onRequest( (request, response) => {
cors(request, response, () => {
let idToken = request.body.data.token;
userModule
.get(idToken)
.then((uid) => {
console.log('User found : ' + uid);
return userModule
.retrieve(uid)
.then((userTokens) => {
console.log('User tokens found : ' + userTokens.token);
return userModule
.me(userTokens.token, uid)
.then((me) => {
return me;
}).catch((error) => {
return response.status(404).json({
data : {
error : 404,
message : 'NO_USER_ON_API'
}
});
})
}).catch((error) => {
console.log(error);
return response.status(404).json({
data : {
error : 404,
message : 'NO_TOKEN_USER_FOUND'
}
});
})
})
.catch((error) => {
console.log(error);
return response.status(500).json({
data : {
error : 500,
message : 'USER_TOKEN_NO_MATCH'
}
});
})
.then((user) => {
if(user.data !== undefined)
{
return response.status(200).json({
data : {
user : user.data
}
});
}
else
{
return response.status(204).json({
data : {
user : null
}
});
}
})
});
});
/* user.js */
exports.get = (firebaseToken) {
return admin.auth().verifyIdToken(firebaseToken)
.then(function(decodedToken) {
return decodedToken.uid;
})
.catch(function(error) {
throw {
code: 500,
body: "INTERNAL_ERROR"
};
});
};
exports.retrieve = (uid) {
return admin.firestore().collection("AccessTokenCollection").doc(uid).get()
.then(function(docRef) {
return docRef.data();
})
.catch(function(error) {
throw {
code: 404,
body: "NO_USER_FOUND"
};
});
};
exports.me = (UserToken, uid) {
let params = {
params: {
},
headers: {
'Authorization': 'Bearer ' + UserToken
}
};
return axiosInstance.instance.get(url + '/users/me', params)
.then(userMe => {
return userMe;
})
.catch(errMe => {
console.log(errMe.response.status);
throw {
code: 401,
body: "EXPIRING_TOKEN"
};
});
};
Etc...
The code works as it is more a theoretical question or optimization!
const userModule = require('./user');
exports.me = functions.https.onRequest((request, response) => {
cors(request, response, async () => {
let idToken = request.body.data.token;
try {
let uid = await userModule.get(idToken);
console.log('User found : ' + uid);
let userTokens = await userModule.retrieve(uid);
console.log('User tokens found : ' + userTokens.token);
let meObj = await userModule.me(userTokens.token, uid);
} catch (error) {
console.log('error', error);
}
});
});
So, here using async-await i have removed then-catch block. await keyword will work as then and will only move forward to second call after first call has been completed. And i have made a common catch block for error handling which you can modified according to your needs
you can use promise.all and async-await instead of then and catch
I am developing an API to create a warehouse structure. Because we are using a microservice architecture I need to make a request via rabbitmq to another microservice to generate the address for the new warehouse.
Therefore I use the ampq consume function wrapped in a function which returns a promise. When I hit the endpoint the first time the promise gets resolved and I can continue with my data. But in the second request, the promise will not get resolved.
Maybe it's for an obvious reason but at the moment I don't get it.
So here is my code:
routes.js
router.post('/', (req, res) => {
...
const validate = ajv.compile(warehoseSchema);
const valid = validate(req.body);
if (valid) {
sendToQueue('addressMgmt', req.body.address);
consume()
.then((result) => {
const {
id_address: idAddress,
license_plate: licensePlate,
town,
} = result.body;
createWarehouseHandler(
customernumber, req.body, idAddress, licensePlate, town,
)
.then((insertId) => {
res.json({
id: 'warehouses02',
message: `Warehouse with id ${insertId} successfully created`,
});
})
.catch((err) => {
res.status(err.status).json({
id: err.id,
message: err.message || err.sqlMessage,
});
});
}).catch((err) => {
res.status(err.status).json({
id: err.id,
message: err.message || err.sqlMessage,
});
});
} else {
res.status(417).json({
id: 'warehouses01',
message: `Invalid JSON: ${ajv.errorsText(validate.errors)}`,
});
}
});
const consume = () => new Promise((resolve, reject) => {
const q = 'warehouseMgmt';
amqpCh.consume(q, (msg) => {
const message = JSON.parse(msg.content.toString());
if (Object.keys(message).includes('body')) {
resolve(message);
} else {
const err = new Error();
err.status = 500;
err.id = 'rabbit01';
err.message = 'No message was cosumed';
reject(err);
}
}, { noAck: true });
});
On the first request consume().then() gets called but on the second and following requests, it doesn't.
Thanks for your help
I am creating a results object to return a boolean and a string. it is not being reassigned within the if statement. The rest of the code is working correctly and the password gets updated or the right error message is output to the console
i've tried leaving result undefined before the try catch.
async function passwordUpdate(password, currentPassword, newPwd, email) {
let hashedPassword = await bcrypt.hash(newPwd, 10);
let result = { success: false , message: ' '};
try {
bcrypt.compare(currentPassword, password, async function (err, res) {
if (res) {
let updateResult = await updatePwd(hashedPassword, email);
if (updateResult) {
result = { success: true , message: 'Password was updated successfully.'};
}
else {
logger.info('Password was not updated successfully.');
}
} else {
logger.error('Passwords do not match');
result= { success: false , message: 'Your current password was entered incorrectly'};
logger.error(result.message);
}
});
} catch (error) {
result= { success: false , message: 'Failed to compare passwords'}
}
logger.error('result ', result.message);
return result;
}
code is being called by this method
app.post('/passwordUpdate', async (req, res) => {
let pwd = req.body.password;
let cpwd = req.body.currentPwd;
let newPwd = req.body.newPwd;
let email = req.body.email;
try {
let result = await usersModel.passwordUpdate(pwd, cpwd, newPwd, email);
console.log(result, result.success, result.message);
if (result.success) {
res.status(200).json({error: result.message});
}
else {
res.status(404).json({error: result.message});
}
} catch (error) {
console.log(error);
}
});
logger.error(result.message); this line within the else statement is outputting the message as expected but
logger.error('result ', result.message); after the try/catch is outputting a blank message for result.message
When you're doing this:
let result = await usersModel.passwordUpdate(pwd, cpwd, newPwd, email);
The passwordUpdate function is resolving the promise based on what is in that function's "top level". This means that the callback function of bcrypt.compare doesn't affect the return of passwordUpdate which is why you're not seeing the result you're looking for.
What you can do is wrap the entire thing in a promise and call the resolve/reject inside the bcrypt.compare function.
async function passwordUpdate(password, currentPassword, newPwd, email) {
return new Promise(async function(resolve, reject) {
let hashedPassword = await bcrypt.hash(newPwd, 10)
try {
bcrypt.compare(currentPassword, password, async function(err, res) {
if (res) {
let updateResult = await updatePwd(hashedPassword, email)
if (updateResult) {
resolve({
success: true,
message: 'Password was updated successfully.',
})
} else {
reject({
success: false,
message: 'Password was not updated successfully.',
})
}
} else {
reject({
success: false,
message: 'Your current password was entered incorrectly',
})
}
})
} catch (error) {
reject({ success: false, message: 'Failed to compare passwords' })
}
})
}
You'll also notice I removed the logging, you can put these back if you wish but since this is a promise you can log your errors more centrally from the caller in the .then and .catch or try/catch if you're using async/await.
Also may I suggest that since you'll be able to determine whether it's an error or not based on the resolve and reject, that you can remove the success from the result and only return a string, it'll make the code cleaner:
async function passwordUpdate(password, currentPassword, newPwd, email) {
return new Promise(async function(resolve, reject) {
const hashedPassword = await bcrypt.hash(newPwd, 10)
try {
bcrypt.compare(currentPassword, password, async function(err, res) {
if (res) {
const updateResult = await updatePwd(hashedPassword, email)
if (updateResult) {
resolve('Password was updated successfully.')
} else {
reject('Password was not updated successfully.')
}
} else {
reject('Your current password was entered incorrectly')
}
})
} catch (error) {
reject('Failed to compare passwords')
}
})
}
PS: I didn't test the code pasted above, I only modified your code to better explain it.
0) I have an HTTP trigger:
exports.checkout = functions.https.onRequest((req, res) => {
1) Update top up transactions when user buys topUp package:
admin.database().ref('topupTransaction').push(topUpObject)
2) Get the user object (with account balance)
admin.database().ref('/users/' + userID).once("value",snap=> {
3) Set the new user object (with new account balance)
admin.database().ref('/users/' + req.query.userUid).set(topUpObject);
I'm not sure how to run all these (1,2,3) sequentially and return a value to the client (0). Hitting shouldn't embed promise. There's Promise.all, but how to use it in this case.
Hitting "Avoid nesting promises." when I try to do it in this way:
exports.checkout = functions.https.onRequest((req, res) => {
var nonceFromTheClient = req.body.payment_method_nonce;
var topUpObject = {
amount : parseInt(req.query.topUpPackage),
date : admin.database.ServerValue.TIMESTAMP, // 1525451616097
user : req.query.userUid
};
admin.database().ref('topupTransaction').push(topUpObject)
.then((topUpResult) => {
return admin.database().ref('/users/' + userID).once("value");
}).then((oldUserData)=>{
return admin.database().ref('/users/' + req.query.userUid).set(topUpObject).then((newUserData)=>{
return res.send(newUserData.val());
})
;
}).catch((error) => {
// Update databse failed (top up transaction)
console.log('Error sending message:', error);
return res.status(500).send(error);
});
Update
using Promise.all, but working partially with errors:
// Create 2 functions
function asyncFunction1(topUpObject){
// Push top up object to database
admin.database().ref('topupTransaction').push(topUpObject)
.then((response) => {
// Update databse successful (top up transaction)
console.log('Top Up transaction created successfully!', topUpObject);
// return res.redirect(303, response.ref);
return topUpObject;
}).catch((error) => {
// Update databse failed (top up transaction)
console.log('Error sending message:', error);
return error;
});
}
function asyncFunction2(userID,topUpObject){
// Get the user account balance
console.log('Current User ID: ', userID);
var ref = admin.database().ref('users').child(userID);
admin.database().ref('/users/' + userID).once("value",snap=> {
// do some stuff once
console.log('Current User Data',snap.val());
console.log('Current User balance',snap.val().accountBalance);
var userContents = snap.val();
var currentBalance = userContents.accountBalance;
var updatedBalance = currentBalance + topUpObject.amount;
console.log('Updated Balance',updatedBalance);
userContents.accountBalance = updatedBalance;
/*Current User Data {
accountBalance: 0,
accountCurrency: 'MYR',
createdOn: '2018-05-02T20:42:49Z',
phoneNumber: '+123445555555'
}
*/
admin.database().ref('/users/' + userID).set(userContents).then(snapshot => {
console.log('Updated top up value! for user', topUpObject);
return res.send(topUpObject.amount);
}).catch((error) => {
// Update databse failed (top up transaction)
console.log('Error sending message:', error);
return error;
});
});
}
// app.post("/checkout", function (req, res) {
exports.checkout = functions.https.onRequest((req, res) => {
var nonceFromTheClient = req.body.payment_method_nonce;
// Use payment method nonce here
// Create Transaction
gateway.transaction.sale({
amount: req.query.topUpPackage,
paymentMethodNonce: nonceFromTheClient,
options: {
submitForSettlement: true
}
},(err, result) => { //TODO: What should we pass back here???
if (err) {
// If top up error (from braintree)
console.log(err.stack);
}else{
// If top up is successful
console.log('Result:',result);
console.log('Top Up Package is: ', req.query.topUpPackage);
var topUpObject = {
amount : parseInt(req.query.topUpPackage),
date : admin.database.ServerValue.TIMESTAMP, // 1525451616097
user : req.query.userUid
};
return Promise.all([asyncFunction1(topUpObject), asyncFunction2(req.query.userUid,topUpObject)]); //TODO: how to pass back res() to client???
}
// Return the error as response
return res.send(err);
});
});
exports.client_token = functions.https.onRequest((req, res) => {
// a token needs to be generated on each request
// so we nest this inside the request handler
// Need to write a function to return a client token,
// and return it back by using the res.send command
console.log('Log customerId',req.query.text);
gateway.clientToken.generate({
// customerId: req.query.text
}, (err, response) => {
// error handling for connection issues
if (err) {
console.log(err.stack);
}else{
clientToken = response.clientToken;
console.log('Log Client token:',clientToken);
return res.send(clientToken);
}
return res.send(err);
});
// return null;
});
There are, in my humble opinion, a couple of things to be fine-tuned in your code, as follows. I've commented in the code.
However, note that since we don't know from where topUpObject comes from (and what it is) it is a bit difficult to be more precise. If you share more details (the full Cloud Function code and the database structure) I may be more precise.
admin.database().ref('topupTransaction').push(topUpObject)
.then(topUpResult => {
return admin.database().ref('/users/' + userID).once("value");
})
.then(oldUserData => {
return admin.database().ref('/users/' + req.query.userUid).set(topUpObject);
//Set returns a non-null firebase.Promise containing void
})
.then(() => { //so I don't think you can get the newUserData here
//return res.send(newUserData.val()); <- and therefore you cannot do that here
//But if I understand well you want to send back to the user the topUpObject, so do as follows:
return res.status(200).send(topUpObject); //<- note the addition of status(200) here
})
.catch((error) => {
// Update databse failed (top up transaction)
console.log('Error sending message:', error);
return res.status(500).send(error);
});
I send data from my input fields to my api:
$.ajax({
url: '/api/login',
type: 'GET',
dataType: 'json',
ContentType: 'application/json',
data: {formData},
success: (data) => {
console.log('SUCCESS')
console.log(data)
this.setState({
isInProcess: false
})
},
error: (jqXHR) => {
console.log(jqXHR)
console.log('ERROR')
this.setState({isInProcess: false})
}
})
on my server-side I have a function to see if I have required user in db:
async function findUser(data) {
try {
const user = await User.findOne({email: data.email,
password: data.password})
console.log('User was found')
return { user }
} catch (err) {
console.log('error', err)
throw err
}
}
which will be executed here:
app.get('/api/login', async (req, res) => {
const data = req.query
try {
const foundUserData = await findUser(data.formData)
return res.json(foundUserData)
} catch (err) {
return res.status(400).json(err)
}
})
It works fine, but if a user wasn't found in db i sends success anyway. Why?
await findUser(data.formData) won't throw error, return either null or user object. You may check something following
app.get('/api/login', async (req, res) => {
const data = req.query
try {
const foundUserData = await findUser(data.formData)
if(foundUserData && foundUserData.user) {
return res.json(foundUserData)
} else {
return res.status(400).json({message: 'User not found'});
}
} catch (err) {
return res.status(500).json(err)
}
})
It sends success because none of your queries error'ed, just because it didn't find anything does not mean that the query failed because it obviously succeeded in finding out if what ever you're looking for exists or not.
To send an error in case of not found you need to check if response is empty in which case you want to send error
When no user is find you get a null value. You may try to put more logic on your success parameter with that for example:
success: function (data) {
if(!!data && data != null) {
alert('Success');
} else {
alert('No data');
}
}