Business
Jobs
  • About Us
  • Solutions
    • Job Postings
      Post your job and receive qualified candidates in 48h.
    • Candidate Assessments
      500+ technical and psychological tests, plus anti-fraud.
    • Headhunting
      Tailor-made executive search from start to finish.
    • Payroll + EOR
      Payroll dispersal and EOR across 15+ LATAM countries.
  • Pricing
  • Jobs

0

169
Views
What is the best way to handle two async calls that must both pass and make an "irreversible" change?

I am currently wondering about this issue, I have a team to which I want to add a user (so write a new user to the database for that team) and I also want to increase the amount of users that team needs to pay for (I use stripe subscriptions).

async handleNewUser(user, teamId){
  await addUserToTeamInDatabase(user, teamId)

  await incrementSubscriberQuantityInStripe(teamId)
}

The problem is, which one do I do first? I recently ran into an issue where users were being added but the subscriber count was not increasing. However, if I reverse them and increment first and then write to database and something goes wrong in this last part, the client pays more but does not get a new member added. One possible way of approaching this is with try catch:

handleNewUser(user, teamId) {
    let userAddedToDatabase = false
    let userAddedInStripe = false

    try {
      await addUserToTeamInDatabase(user, teamId)
      userAddedToDatabase = true
      await incrementSubscriberQuantityInStripe(teamId)
      userAddedToStripe = true
    } catch (error) {
      if (userAddedToDatabase && !userAddedInStripe) {
        await removeUserFromTeamInDatabase()
      }
    }
  }

So I'm writing the new user to the database and then making a call to the stripe API. Is there a better way to approach this because it feels clumsy. Also, is there a pattern to address this problem or a name for it?

I'm using Firebase realtime database.

Thanks everyone!

about 4 years ago · Juan Pablo Isaza
2 answers
Answer question

0

What you want to perform is a transaction. In databases a transaction is a group of operations that is successful if all of its operations are successful. If at least one operation fails, no changes are made (all the other operations are cancelled or rolled back).

And Realtime Database supports transactions! Check the documentation

about 4 years ago · Juan Pablo Isaza Report

0

If both operations would be in the same database you'd normally bundle them in a transaction and the DB will revert to initial state if one of them fails. In your case you have operations in different external systems (DB & Stripe) so you'll have to implement the transactional logic yourself.

You could simplify your example by checking where the error comes from in the catch clause. Then you can get rid of the flags. Something like this:

handleNewUser(user, teamId) {
  try {
    await addUserToTeamInDatabase(user, teamId)
    await incrementSubscriberQuantityInStripe(teamId)
  } catch (error) {
    // If we fail to increment subscriber in Stripe,
    // cancel transaction by removing user from DB
    if (instanceof error StripeError) {
      await removeUserFromTeamInDatabase(user, teamId)
    }

    // Re-throw error upstream
    throw error;
  }
}

I use instanceof but you you change the conditional logic to fit your program.

about 4 years ago · Juan Pablo Isaza Report
Answer question
Find remote jobs

Discover the new way to find a job!

Top jobs
Top job categories
Business
Post vacancy Pricing Sales
Legal
Terms and conditions Privacy policy
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Show me some job opportunities
There's an error!