I have a callable initiatePayment function below that process the payment of a user when they pay from client side. This records a new document to my firestore database if the payment is success (addNewRecord) then finally returns payment data from the response of the request.
export const initiatePayment = functions.https.onCall(async (data, context) => {
// destructure data argument
const { userId } = data;
try {
// 1. Process payment via external API request
const payment = await paymentExternalRequest();
const { paymentData, status } = payment.response;
// 2. If payment processing was a success, record new payment data
if (status === "succeeded") {
addNewRecord(userId, paymentData);
}
// 3. Return paymentData to client
return paymentData;
} catch (error) {
throw new functions.https.HttpsError("cancelled", "Cancelled", error);
}
});
addNewRecord function:
const addNewRecord = async (userId, paymentData) => {
const newRecordToAdd = { userId, paymentData };
const docRef = admin
.firestore()
.collection("transactions")
.doc(paymentData.id);
try {
const newRecord = await docRef.set({ userId, transaction: newRecordToAdd });
return newRecord;
} catch (error) {
console.log(error);
}
};
My question is what if addNewRecord fails, how do you handle its error and retry the function again to ensure its success?
You should not have problems with the addNewRecord failing, considering your code. Due to the fact that the function will only be called, based in specific and controlled scenarios, in which you will have the parameters needed for the function to be called correctly, you should be fine.
Anyway, it's very probably that if it failed once, it will fail again, so, you can try to work with a queue system instead of just trying to repeat the execution. This way, you will maintain that data in a queue and run again after checking and handling of the error, to ensure that the addition of the record will occur.
I would recommend you to take a look at the following documents, on queuing with Javascript, that I believe might help you.
Implementation of Queue in Javascript
How would I design a client-side Queue system?
Let me know if the information helped you!
Related
I’m working on an application where I need to make requests to two apis. I’m using cognito to handle authentication, and then a lambda that communicates with a database. But, I don’t think that my problem is specific to either of those implementations. It could arise with any two apis.
I’m trying to write the process of signing up a new user. I need to create a new user in cognito so that the new user is able to login, and then I need to create a corresponding user in the database that will store the non-authentication related data for the user. If one of the api requests encounters an error, then I need to delete the item that I created in the other api.
My current implementation is basically this:
const signUpNewUser = (userInfo) => {
API.post("user", "/user", userInfo)
.then((response) => {
return COGNITO.post("user", "/user", response.newUserID);
})
.then((res) => {
//BOTH REQUESTS OCCURED WITH NO ERRORS
})
.catch((error) => {
if (error.origin === "COGNITO_ERROR") {
//IF DB CHANGES CONFIRMED BUT COGNITO FAILED, DELETE CREATED GUEST IN DB
return API.delete("guest", "/guest", userInfo);
} else if (error.origin === "DATABASE_ERROR") {
//IF DB CHANGES FAILED THEN COGNITO HAS NOT RUN YET, SO DON'T NEED TO DELETE IN THIS CASE
}
});
};
This follows the pattern I see demonstrated on the internet. However, I’m having trouble distinguishing cognito errors from database errors. In the above code I sort them by error.origin but they don’t actually have a property that reliably indicates their origin. This problem must be common when working with multiple api’s that you don’t control, but I’m having trouble finding a good solution.
It feels I need to nest promises in this situation. I could nest a catch after API.Post and COGNITO.post, and use that catch to throw a new error that has an origin property. Then that would bubble up and get caught by the final catch that handles all errors. Like this:
const signUpNewUser2 = (userInfo) => {
API.post("user", "/user", userInfo)
.catch((err) => {
let parsedError = err;
parsedError.origin = "DATABASE_ERROR";
throw parsedError;
})
.then((response) => {
let newGuestID = response.id;
return COGNITO.post("user", "/user", newGuestID)
.then((res) => {
return res;
})
.catch((err) => {
let parsedError = err;
parsedError.origin = "COGNITO_ERROR";
throw parsedError;
});
})
.then((res) => {
//BOTH REQUESTS OCCURED WITH NO ERRORS
})
.catch((error) => {
if (error.origin === "COGNITO_ERROR") {
//IF DB CHANGES CONFIRMED BUT COGNITO FAILED, DELETE CREATED GUEST IN DB
return API.delete("guest", "/guest", guestInfo);
} else if (error.origin === "DATABASE_ERROR") {
//IF DB CHANGES FAILED THEN COGNITO HAS NOT RUN YET, SO DON'T NEED TO DELETE IN THIS CASE
}
});
};
But everything I've read says you should avoid nesting promises.
Alternatively, I could put API.post and COGNITO.post in separate functions with internal .then .catch statements, and then have those functions return a promise or throw an error with an added property to indicate origin. But I've seen people say that just hides the problem and makes code harder to follow.
The standard patter I see is that you have one catch, towards the end of a .then chain, that knows how to handle multiple kinds of errors. But if you don’t control the APIs you are working with, how can you confidently sort those errors? Is there something basic about the nature of errors in js that I'm missing?
Because you want to make the API calls in serial, this should be pretty easy to manage. All you need to do is do COGNITO.post in a .then after the first API call - no need to insert another .catch in between.
const signUpNewUser2 = (userInfo) => {
API.post("user", "/user", userInfo)
.then((response) => {
let newGuestID = response.id;
return COGNITO.post("user", "/user", newGuestID)
.then(handleBothSuccess)
.catch((err) => {
// COGNITO failed
return API.delete("guest", "/guest", guestInfo);
});
})
.then((res) => {
//BOTH REQUESTS OCCURED WITH NO ERRORS
})
.catch((error) => {
// Some error other than COGNITO failing occurred
});
};
There's nothing wrong with nesting Promises when the control flow you need to implement requires it - or with declaring the .then or .catch functions in separate standalone variables first, which avoids visual nesting.
Alternatively, consider async/await, which may be clearer to follow.
const signUpNewUser2 = async (userInfo) => {
let newGuestId;
try {
newGuestId = await API.post("user", "/user", userInfo);
} catch (e) {
// API failed, do something here if you want...
return;
}
let cognitoResponse;
try {
cognitoResponse = await COGNITO.post("user", "/user", newGuestID);
} catch (e) {
// COGNITO failed
// If deleting throws, the error will percolate to the caller
return API.delete("guest", "/guest", guestInfo);
}
//BOTH REQUESTS OCCURED WITH NO ERRORS
};
I am running this node.js code to create customer on stripe account function deploy successful but failed to create customer on stripe I am not getting what I am missing.
Fire base functions folder is also not showing the function there.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
const stripe = require('stripe')("secret key here");
var customer;
stripe.customers.create(
{
email: 'customer#example.com',
},
{
maxNetworkRetries: 2,
}
);
When you use APIs to services (viz. stripe.com and firebase) outside your complete control, you must check for errors. If you're using a service incorrectly the error will explain, or at least hint, what you're doing wrong.
The stripe-node API documentation suggests you invoke stripe.customer.create() as an awaited function, like this:
const customer = await stripe.customers.create({
email: 'customer#example.com',
});
This is easy if you call it from an async function. You should use this sort of code in your async function to check for errors back from stripe.com.
try {
const customer = await stripe.customers.create({
email: 'customer#example.com',
});
/* here's your customer object */
}
catch (error) {
console.error ('stripe', error);
}
If you do not call it from an async function, you can wait for results using the Promises scheme.
stripe.customers.create({
email: 'customer#example.com',
})
.then ( function (customer) {
/* here's your customer object */
})
.catch ( function (error) {
console.error ('stripe', error);
});
If you haven't yet figured out how async/await or Promises work, the time has come for you do to that.
I'm trying to build a JSON file by making successive HTTP requests with Axios:
Get an array of objects (projects)
Create an array property in each project named attachments
Get each project's tasks
Get each task's attachments
Push each project's task's attachments in to the project's attachments array
Create a JSON file out of the modified projects array
Code:
let getProjects = function() {
try {
return axios.get('https://app.asana.com/api/1.0/projects/')
} catch (error) {
console.error(error)
}
}
let getTasks = function(project) {
try {
return axios.get('https://app.asana.com/api/1.0/projects/'+project+'/tasks')
} catch (error) {
console.error(error)
}
}
let getAttachments = function(task) {
try {
return axios.get('https://app.asana.com/api/1.0/tasks/'+task+'/attachments')
} catch (error) {
console.error(error)
}
}
async function getAsanaData() {
let projects = await getProjects()
return Promise.all(projects.data.data.map(async (project) => {
project.attachments = []
let tasks = await getTasks(project.gid)
return Promise.all(tasks.data.data.map(async (task) => {
let attachments = await getAttachments(task.gid)
project.attachments = !!attachments ? project.attachments.concat(attachments.data.data) : project.attachments
return project
}))
}))
}
getAsanaData()
.then((projects) => {
var asanaData = safeJsonStringify(projects);
fs.writeFile("thing.json", asanaData);
})
.catch(err=>console.log(err))
But I'm running into this error:
status: 429,
statusText: 'Too Many Requests
I haven't found anything helpful yet for figuring out how to resolve it. What can I do?
HTTP response status code 429 indicates sending too many requests than what server could handle. It has been documented at https://asana.com/developers/documentation/getting-started/errors too. The maximum allowed is 150 per minute as documented at https://asana.com/developers/documentation/getting-started/rate-limits.
So, yes, as #Randy Casburn commented, you will have to throttle your requests.
You're getting throttled by Asana for sending too many requests and reaching the maximum rate.
When it happens, you need to check for the Retry-After response header and wait for the specified amount of time before sending another request.
https://asana.com/developers/documentation/getting-started/rate-limits
You can also learn more in the RFC 6585 about HTTP 429
I am having challenges retrieving the results of my mutation. I need to create a db record and send an email notifying to user that the registration was successful. since both the sending of the email and the db update is server side I want to do both in the same mutation. If the email message fail the db must not be updated. So I have the following Mutation:
Mutation: {
createDealer(_, params) {
console.log("params: " + JSON.stringify(params));
bcrypt.genSalt(10, function(err, salt) {
bcrypt.hash(params.dealer.password, salt, function(err, hash) {
// Store hash in your password DB.
console.log("hashed password " + params.dealer.password)
params.dealer.password = hash;
console.log("hashed password " + params.dealer.password + " Hash: " + hash);
let session = driver.session();
let query = "CREATE (d:Dealer {email:$dealer.email}) SET d += $dealer RETURN d";
let here = "here".link("mymail#example.com");
let messageObj = {
to: params.dealer.email,
subject: 'Dealer Registration',
text: `Thank you for signing up. To complete and activate your registration please click ${here}.`
}
return (sendEmail(messageObj))
.then(data => {
console.log('SendMail data....' + JSON.stringify(data));
return session.run(query, params)
})
.then(result => {
console.log('SendNeo4j data....' + JSON.stringify(result));
return result.records[0].get("d").properties
})
.catch((err) => {
console.log(err);
});
//});
});
}); // genSalt
} // Create Dealer
}, // Mutation
Even thought both actions are successful I can't seem to retrieve the results. I get 'undefined' for:
console.log('SendMail data....' + JSON.stringify(data));
while
console.log('SendNeo4j data....' + JSON.stringify(result));
does display the correct data
but graphiql returns 'null' for the mutate.
this is the graphiql mutation:
mutation CreateDealer($dealer: DealerInput!) {
createDealer(dealer: $dealer) {
email
name
}
}
with the DealerInput variables of course.
I have read where you can retrieve multiple results from a query/mutation but I am not sure how it works. Here I need both the results of the sendEmail and the db update for my Angular/apollo front-end....I would imaging graphiql knows nothing of the sendEmail but I expected it to return the properties I requested.
SendEmail:
module.exports = (message) =>
new Promise((resolve, reject) => {
const data = {
from: 'mymail#example.com',
to: message.to,
subject: message.subject,
text: message.text
};
mailgun.messages().send(data, (error) => {
if (error) {
return reject(error);
}
return resolve();
});
});
Can someone with a little more experience than I help me out here...thanks
Couple of things to fix here. Returning a Promise (or any other value) inside a callback doesn't do anything, and doing so won't let you chain additional Promises like you want. Instead, your promise gets fired off inside the callback and isn't awaited.
As a general rule of thumb, don't mix Promises and callbacks. If you absolutely have to use callbacks, always wrap the callback in a Promise (like you did inside sendMail). Luckily, most popular libraries today support both callbacks and Promises. Here's how you could refactor the code above to correctly chain all your Promises:
createDealer(_, params) {
return bcrypt.hash(params.dealer.password, 10) // note the return here!
.then(hash => {
params.dealer.password = hash
const session = driver.session()
const query = "CREATE (d:Dealer {email:$dealer.email}) SET d += $dealer RETURN d"
const here = "here".link("mymail#example.com")
const messageObj = {
to: params.dealer.email,
subject: 'Dealer Registration',
text: `Thank you for signing up. To complete and activate your registration please click ${here}.`
}
return sendEmail(messageObj) // note the return here!
}).then(data => {
return session.run(query, params) // note the return here!
}).then(result => {
result.records[0].get("d").properties // note the return here!
})
bcrypt.hash will autogenerate the salt for you if you don't pass one in -- there's no need to call two separate functions
We kick off our Promise chain with bcrypt.hash, so we need to return the Promise it returns. A resolver must return a value or a Promise that will resolve to a value, otherwise it returns null.
Inside each then, we return a Promise. This way we "chain" our Promises, allowing the final value we return in the resolver to be the value the very last Promise in the chain resolves to.
We need to also fix your sendMail function to actually return the value. You're correctly returning the new Promise inside the function, but you also need to pass the returned data object to resolve. That tells the Promise to resolve to that value.
module.exports = (message) => new Promise((resolve, reject) => {
const data = // ...etc
mailgun.messages().send(data, (error) => {
if (error) reject(error) // no need to return, it's pointless
resolve(data) // pass data to resolve
})
})
Side note: looks like the official mailgun library supports Promises.
Additionally, I would strongly encourage you to look into using async/await, especially when dealing with a long Promise chain. It's less error prone and more readable:
createDealer async (_, params) {
const hash = await bcrypt.hash(params.dealer.password)
params.dealer.password = hash
const session = driver.session()
const query = "CREATE (d:Dealer {email:$dealer.email}) SET d += $dealer RETURN d"
const here = "here".link("mymail#example.com")
const messageObj = {
to: params.dealer.email,
subject: 'Dealer Registration',
text: `Thank you for signing up. To complete and activate your registration please click ${here}.`
}
const emailResult = await sendEmail(messageObj)
const result = await session.run(query, params)
return result.records[0].get("d").properties // still need to return!
}
EDIT: With regard to catching errors, GraphQL will catch any errors thrown by your resolver, which means you can often skip using catch yourself. For example, if your mailgun request fails, it'll generate some kind of error and your query will return null for data and the error details inside of the errors array.
That may be sufficient, although 1) you may want to log your error's stack elsewhere; and 2) in production, you probably don't want to expose internal error details to the public.
That means you'll probably want to use custom errors. As a bonus, you can add some custom properties to your errors to help the client deal with them eloquently. So your code may end up looking more like this:
class DeliveryFailureError extends Error {}
DeliveryFailureError.code = 'DELIVERY_FAILURE'
DeliveryFailureError.message = 'Sorry, we could not deliver the email to your account'
try {
await mailgun.messages.create()
} catch (err) {
logger.error('Mailgun request failed:', err.stack)
throw new DeliveryFailureError()
}
I created a node.js application that uses the knex library to make database operations. The database is Microsoft SQL Server. I created a script called db.js that returns the knex object, and i have a controller.js script that makes the actually needed database operations. I have all the operations wrapped inside a translation statement, which brings me to my question. What i would like to do, is pass in a parameter that tells the transaction to rollback or commit. However, whenever i try to pass this function in, it just fails. Does anyone know if this feature achievable ? I was able to do this feature with the catch function.
I don't think its db specific, so anyone can download knex, hook it up a db and give it a shot with the code below.
example.js
/**
* #param {userID} Int
* #param {rollback} Boolean
*/
const getUsers = (userID, rollback) => {
// Using tran as a transaction object:
return db('master').transaction((tran) => {
db('master')
.select()
.from('users')
.where({ 'user_id': userID })
.transacting(tran)
.then(tran.rollback) // Works
// .then(transact(tran, rollback)) throws error
.catch((error) => {
logError(error, tran); // Works
});
// .catch(tran.rollback);
});
};
const logError = (error, transaction) => {
transaction.rollback;
console.log('transaction error: ',error);
console.log('transaction log: ',transaction);
};
const transact = (transaction, rollback) => {
try {
if (rollback) return transaction.rollback;
else return transaction.commit;
} catch (error) {
console.log(error);
}
};
const user = await getUsers(1, true); // error is thrown
assert.strictEqual(user.constructor === Array, true);
assert.strictEqual(user.length == 0, true);
Error Message
Error: the array [ {
"user_id": 1
"user_name": "JonnyBoy"
"zip": 1200
"email": "jjboy#test.com"
} ] was thrown, throw an Error :)
then takes a function as its first parameter. If the Promise resolves, the function that's passed to then is called with the value the Promise resolved to. The rollback property on the transaction object is a function so you can write:
someQuery().then(trans.rollback)
and rollback will be called when someQuery resolves. You could also write:
someQuery.then(result => trans.rollback(result))
These statements are equivalent.
That said, minimally, there's two changes that you need to make. One, fix how you're calling transact. For example:
.then(() => transact(tran, rollback))
And change how transact calls rollback:
const transact = (trx, rollback) => {
const action = rollback ? trx.rollback : trx.commit
return action()
}
Also bear in mind that rollback itself will return a rejected Promise with a generic error. You can pass a custom error to rollback and it will reject with that error instead.