I am creating a loop to create/update users using functions with built-in promises:
for (const user of usersjson.users) {
let getuser = getUser(url, okapikey, user[fieldMap.externalSystemId],
'externalSystemId'); //Check if user exists on the server
await getuser
.then(async (data) => {
if (data.users.length != 0) { //If user exists in the array
update = updateUser(url, okapikey, createduser, data.users[0].id);//Create update function
promises.push(update); //Store function in array
i++;
} else {
create = createNewUser(url, okapikey, createduser);//Create create function
promises.push(create); //Store function in array
i++;
}
}).catch((err) => {
console.error(err);
});
if (promises.length == 50 || i == usersjson.users.length) {//Run functions in batches of 50
await Promise.allSettled(promises)
.then((responses)=> {
for (const response of responses) { //For each promise response
if (response.status == 'fulfilled') { //If fulfilled
if (response.value.status == 204) {
console.log(`${response.value.status}: User ${response.value.request.path.substring(7)} was updated.`);
} else {
if (response.value.status == 201 && response.value.headers.location) {
console.log(`${response.value.status}: User ${response.value.headers['location']} was created.`);
} else {
console.log(response.value.headers.location);
}
}
} else { //Handle rejections
console.log(`There was an error with the user:${response.value}`);
}
}
}).catch((err)=> {
console.log(err);
});
promises=[]; //Empty Promise array
}
}
async function updateUser(url, token, user, userid)
{
return new Promise((resolve, reject) => {
//Create headers for put request
const options = {
method: "put",
headers: {
'x-okapi-token': token,
'x-okapi-tenant':'tenant',
'Content-type':"application/json"
}
};
//Make API get call
user.id=userid; //Adding the required field ID to the JSON
axios.put(`${url}/users/${userid}`, JSON.stringify(user), options)
.then(response => {
if (response.status == 204) {
resolve(response);
} else {
reject(`Error Code: ${err.response.status}\nError Text: ${err.response.data.errors[0].message}\nError Status: ${err}`);
}
}).catch((err) => {
console.error(`Error Code: ${err.response.status}`);
if (typeof err.response.data == 'string') {
console.error(err.response.data);
reject(`Error Code: ${err.response.status}\nError Text: ${err.response.data.errors[0].message}\nError Status: ${err}`);
} else if (err.response.data.errors[0].message) {
console.error(`Error Text: ${err.response.data.errors[0].message}`);
reject(`Error Code: ${err.response.status}\nError Text: ${err.response.data.errors[0].message}\nError Status: ${err}`);
} else {
reject(`Error Code: ${err.response.status}\nError Text: ${err.response.data.errors[0].message}\nError Status: ${err}`);
}
console.log(err.response);
});
});
};
async function createNewUser (url, token, user) {
return new Promise((resolve, reject) => {
//Create headers for put request
const options = {
headers: {
'X-Okapi-token': token,
'Content-type':"application/json"
}
};
//Make API get call
axios.post(`${url}/users`, JSON.stringify(user), options)
.then(response => {
if (response.status == 201) {
resolve(response);
} else {
reject(`Error Code: ${err.response.status}: ${user.externalSystemId},\nError Text: ${err.response.data.errors[0].message},\nError Status: ${err}`)
}
}).catch((err) => {
console.error(`Error on ${user.externalSystemId}: ${err}`);
if (err.response.data && typeof err.response.data == 'string') {
console.error(err.response.data);
reject(`Error Code: ${err.response.status}: ${user.externalSystemId},\nError Text: ${err.response.data.errors[0].message},\nError Status: ${err}`)
} else if (err.response.data.errors[0].message) {
console.error(`Error Text: ${err.response.data.errors[0].message}`);
reject(`Error Code: ${err.response.status}: ${user.externalSystemId},\nError Text: ${err.response.data.errors[0].message},\nError Status: ${err}`)
} else {
reject(`Error Code: ${err.response.status}: ${user.externalSystemId},\nError Text: ${err.response.data.errors[0].message},\nError Status: ${err}`)
}
});
});
};
const getUsers = (url,user,password) =>
{
return new Promise((resolve, reject) => {
//Create headers for POST request
const options = {
method: 'post',
headers: {
'Authorization': 'Basic '+Buffer.from(`${user}:${password}`).toString('base64')
}
}
//Make API get call
axios.get(url, options)
.then(response => {
resolve(response.data);
}).catch((err) => {
console.error(err);
reject(err);
});
});
};
The code and loop works fine when every promise is fulfilled, but once a promise is rejected, the loop breaks. I get the error message, for example:
Error on XXX: Error: Request failed with status code 422 Error Text:
User with this username already exists
node:internal/process/promises:246
triggerUncaughtException(err, true /* fromPromise */);
^
[UnhandledPromiseRejection: This error originated either by throwing
inside of an async function without a catch block, or by rejecting a
promise which was not handled with .catch(). The promise rejected with
the reason "Error Code: 422: XXX, Error Text: User with this username
already exists, Error Status: Error: Request failed with status code
422".] { }
Looking at the code and error, I believe this comes from the "createNewUser" function.
I'm not sure why the code breaks - I added catches to all the functions, handled rejections, and added catch statements in the main body of the code, but the loop still breaks.
What I need is for the loop to continue as usual even if one function fails (I will later change the log from console.log to an actual log file).
update = updateUser(url, okapikey, createduser, data.users[0].id);//Create update function
promises.push(update); //Store function in array
create = createNewUser(url, okapikey, createduser);//Create create function
promises.push(create); //Store function in array
This is inaccurate. You are not storing functions in that array, you actually call the updateUser/createNewUser functions here and store the resulting promises in the array. Then, your loop goes on to sequentially (because of the await) do more getUser operations before actually calling Promise.allSettled on the promises array. In the meantime, some of the promises might already have been rejected without having any handlers attached to them.
This is basically the same problem as discussed in Waiting for more than one concurrent await operation and Any difference between await Promise.all() and multiple await?.
To fix it, collect actual functions that you can execute later in your array:
let functions = [];
for (const user of usersjson.users) {
i++;
try {
const data = await getUser(url, okapikey, user[fieldMap.externalSystemId], 'externalSystemId');
if (data.users.length != 0) {
functions.push(() =>
// ^^^^^
updateUser(url, okapikey, createduser, data.users[0].id)
); // Create update function and store it in array
} else {
functions.push(() =>
// ^^^^^
createNewUser(url, okapikey, createduser)
); // Create create function and store it in array
}
} catch(err) {
console.error(err);
}
if (functions.length == 50 || i == usersjson.users.length) { // in batches of 50
const promises = functions.map(fn => fn()); // Run functions
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
const responses = await Promise.allSettled(promises);
for (const response of responses) {
if (response.status == 'fulfilled') {
if (response.value.status == 204) {
console.log(`${response.value.status}: User ${response.value.request.path.substring(7)} was updated.`);
} else {
if (response.value.status == 201 && response.value.headers.location) {
console.log(`${response.value.status}: User ${response.value.headers['location']} was created.`);
} else {
console.log(response.value.headers.location);
}
}
} else {
console.log(`There was an error with the user:${response.value}`);
}
}
functions = []; // empty functions array
}
}
(I've tried to avoid awaiting any .then(…) chains)
Related
I'm trying to develop an API post, in middle execution I have validation such as check name already in use or not. I set error handler callback, it successfully send response 'Already registered', but when I checked to CLI, it show error
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
I dont know whats wrong, I use this error handler in the past and it seems look ok.
Here is my code in router:
createUserAccount: async function (req, res) {
const programData = req.body;
try {
await service.create(programData, function (code, err, result) {
if (err) {
if(code === 409){
res.status(HTTPSTATUS.CONFLICT).send(err.message);
} else {
res.status(HTTPSTATUS.BAD_REQUEST).send(err.message);
}
} else {
res.status(HTTPSTATUS.CREATED).json(result);
}
})
} catch (e) {
console.log(e)
res.status(HTTPSTATUS.BAD_REQUEST).json("Failed.");
}
Here is my function in my service:
const config = require('#configs/config.json')
const sequelize = require('sequelize');
const SEQUELIZE = new sequelize(config[env]);
module.exports = {
createAccount: async (name, password, callback) => {
try {
let check,
institution_id;
const checkName = await Profile.count(
{
where: {
name: name
}
}
);
//result checkName = 1
if(checkName > 0){
//then successfully execute this condition and
return callback(409, 'Already registered.', null);
//this show in console ----> POST /API/v1/user/profile 409 616.152 ms - 31
}
await Login.create({
username: email,
password: password
}).then(resLogin => {
const response = {
id: resLogin.id,
}
callback(201, null, response);
}).catch( error => {
callback(400, error, null);
})
} catch (e) {
callback(400, e, null);
}
},
create: async (payload, callback) => {
let loginID = null;
let {
profile,
address
} = payload;
let {
name,
email,
password
} = profile;
try {
await module.exports.createAccount(name, password, function (code, error, result) {
if(error){
const res = {message: error};
//what I need is the execution is end in here
return callback(code, res, null);
}
loginID = result.id;
});
//but the fact is it still execute this whole function if got callback error from createAccount()
let transaction = await SEQUELIZE.transaction();
await Address.create(address, {transaction})
.then( async resAddress => {
await transaction.commit();
return callback(201, null, resProfile);
}).catch(async e => {
return callback(400, e, null);
})
} catch (e) {
console.log(e);
callback(e, null);
}
};
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'm new to promises, async/await, and Alexa/lambda so bear with me.
My function is returning prior to the data being returned. I had a similar issue where I was getting an error, but have since edited my function quite a bit and therefore asking a new question. I'm now no longer getting an error, but instead my data is being returned first, then the promise is executing.
I've tried re-writing the promise/function after reading many, many SO, google, and amazon developer forums. Nothing seems to be working for me.
const IntentRequest = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'IntentRequest';
},
async handle(handlerInput) {
const { requestEnvelope, serviceClientFactory, responseBuilder } = handlerInput;
let responseData, promise;
checkAuthenticationStatus(handlerInput,function(json){
console.log('waited!')
if(json.error) {
return handlerInput.responseBuilder.speak(messages.NO_ACCESS).withSimpleCard('Unauthorized Request', messages.NO_ACCESS).getResponse();
} else if(json.noerror && json.noerror.okay == 'true'){
console.log('starting to get intent data')
const url = new URL(json.noerror.okay.path);
promise = new Promise((resolve, reject) => {
console.log('start promise')
return httpsGetIntent(handlerInput, url).then((resultData) => {
console.log(resultData)
responseData = resultData;
resolve(responseData)
console.log('inside promise, no error, prior to return data')
})
}).then((result) => { console.log('result', result)})
return handlerInput.responseBuilder.speak('Test').getResponse();
}
});
console.log('response data', responseData)
let result = await promise;
return result;
},
};
Out of my many console.logs() added for debugging, they print as follows:
- 'response data'
- 'waited!'
- 'starting to get intent data'
- 'start promise'
- resultData
- 'inside promise, no error, prior to return data'
While it's not fully fleshed out (I need to address the error handling portion) I wanted to share my solution here in case anyone stumbled upon this post and needed some help.
I moved the promise outside the checkAuthentication function and returned the data when it was processed. I then chained the promise with .then() and passed it the data returned, and prompted Alexa to speak.
const IntentRequest = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'IntentRequest';
},
async handle(handlerInput) {
const { requestEnvelope, serviceClientFactory, responseBuilder } = handlerInput;
let responseData, promise;
return new Promise((resolve, reject) => {
checkAuthenticationStatus(handlerInput, async function(json) {
if (json.error) {
return handlerInput.responseBuilder.speak(messages.NO_ACCESS).withSimpleCard('Unauthorized Request', messages.NO_ACCESS).getResponse();
} else if (json.noerror && json.noerror.okay == 'true') {
const url = new URL(json.noerror.okay.path);
let resultD = await httpsGetIntent(handlerInput, url, function(resultData) {
if (resultData) {
return resolve(resultData);
} else {
return resolve(handlerInput.responseBuilder.speak('False Test').getResponse());
}
})
}
})
}).then((data) => {
return handlerInput.responseBuilder.speak(data.response.outputSpeech.text).getResponse();
});
},
};
Almost_Ashleigh, based on your own answer, some ideas in which I have :
made the assumption that httpsGetIntent() returns a Promsie that delivers resultData.
promisified checkAuthenticationStatus() by writing the adaptor checkAuthenticationStatusAsync().
consolidated the .speak() commands into final .then() and .catch() clauses.
allowed specific errors to be tailored by decorating with a .title property, which is used in the final .catch() as a cardTitle. Any undecorated errors will default to 'Sorry' (or whatever you want).
// in a suitable scope ...
function checkAuthenticationStatusAsync(handlerInput) {
return new Promise((resolve, reject) {
checkAuthenticationStatus(handlerInput, (json) => {
if (json.error || !json.noerror || json.noerror.okay !== 'true') {
let err = new Error(messages.NO_ACCESS); // or maybe a separate error message per error case?
err.title = 'Unauthorized Request';
reject(err);
} else {
resolve(json);
}
});
});
}
// ... and ...
const IntentRequest = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'IntentRequest';
},
handle(handlerInput) {
return checkAuthenticationStatusAsync(handlerInput)
.then((json) => httpsGetIntent(handlerInput, new URL(json.noerror.okay.path)))
.then((resultData) => {
if (resultData) {
// success path ends here
return handlerInput.responseBuilder.speak(resultData.response.outputSpeech.text).getResponse();
} else {
throw new Error('No result data returned'); // throws to the .catch() below
}
})
.catch(err => {
// all errors end up here.
return handlerInput.responseBuilder.speak(err.message).withSimpleCard(err.title || 'Sorry', err.message).getResponse();
throw err; // to keep handle's caller informed
});
},
};
Note, this is a way, not necessarily the way, to write the code. Please feel free to raid for ideas.
Your best friend is async/await. Please use something like this or like this to access APIs.
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.
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');
}
}