Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
664 views
in Technique[技术] by (71.8m points)

node.js - How to handle async concurrent requests correctly?

Let's say I have some sort of game. I have a buyItem function like this:

buyItem: function (req, res) {
    // query the users balance
    // deduct user balance
    // buy the item
}

If I spam that route until the user balance is deducted (the 2nd query) the user's balance is still positive.

What I have tried:

buyItem: function (req, res) {
    if(req.session.user.busy) return false;
    req.session.user.busy = true;
    // query the users balance
    // deduct user balance
    // buy the item
}

The problem is req.session.user.busy will be undefined for the first ~5 requests. So that doesn't work either.

How do we handle such situations? I'm using the Sails.JS framework if that is important.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

Update 2

Sails 1.0 now has full transaction support, via the .getDatastore() method. Example:

// Get a reference to the default datastore, and start a transaction.
await sails.getDatastore().transaction(async (db, proceed)=> {
  // Now that we have a connection instance in `db`, pass it to Waterline
  // methods using `.usingConnection()` to make them part of the transaction:
  await BankAccount.update({ balance: 5000 }).usingConnection(db);
  // If an error is thrown, the transaction will be rolled back.
  // Or, you can catch errors yourself and call `proceed(err)`.
  // To commit the transaction, call `proceed()`
  return proceed();
  // You can also return a result with `proceed(null, result)`.
});

Update

As several commenters have noted, the code below doesn't work when connection pooling is enabled. At the time that this was originally posted, not all of the adapters pooled by default, but at this point it should be assumed that they do, so that each individual method call (.query(), .findOne(), etc.) could be on a different connection, and operating outside of the transaction. The next major version of Waterline will have transaction support, but until then, the only way to ensure that your queries are transactional is to use the raw database driver package (e.g. pg or mysql).

It sounds like what you need is a transaction. Sails doesn't support transactions at the framework level yet (it's on the roadmap) but if you're using a database that supports them (like Postgres or MySQL), you can use the .query() method of your model to access the underlying adapter and run native commands. Here's an example:

buyItem: function(req, res) {
  try {
    // Start the transaction
    User.query("BEGIN", function(err) {
      if (err) {throw new Error(err);}
      // Find the user
      User.findOne(req.param("userId").exec(function(err, user) {
        if (err) {throw new Error(err);}
        // Update the user balance
        user.balance = user.balance - req.param("itemCost");
        // Save the user
        user.save(function(err) {
          if (err) {throw new Error(err);}
          // Commit the transaction
          User.query("COMMIT", function(err) {
            if (err) {throw new Error(err);}
            // Display the updated user
            res.json(user);
          });
        });
      });
    });
  } 
  // If there are any problems, roll back the transaction
  catch(e) {
    User.query("ROLLBACK", function(err) {
      // The rollback failed--Catastrophic error!
      if (err) {return res.serverError(err);}
      // Return the error that resulted in the rollback
      return res.serverError(e);
    });
  }
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...