0

I have a patch endpoint that takes a body of fields to update, iterates over each field, and updates the correct value. I want to do something send a response once all those things are done, but I do not know how to await a loop of operations. If it was a singular operation I could just add .then(), but that would not work in this case. What is an elegant solution to this?

Code:

const updateUser = (req, res) => {
    const db = mongoConnection.getDb();
    for (key in req.body) {
        db.collection('users').updateOne(
            {username: req.query.username},
            {$set: {[key]: req.body[key]}}
        )
    }
    // send response once for loop is done
}
CobaltGecko
  • 188
  • 9

2 Answers2

2

You could mark the outer function async and await each DB update inside the loop. Then you'll know that after the loop completes, all DB updates are done.

An even better way is to run the updates in parallel since they do not depend on each other. You can use Promise.allSettled() which takes an array of promises and resolves when the last one is finished.

const updateUser = async (req, res) => {
  const db = mongoConnection.getDb();
  
  const dbUpdates = Object.entries(req.body).map((key, value) => {
    return db.collection('users').updateOne(
      { username: req.query.username },
      { $set: { [key]: value }}
    );
  });

  await Promise.allSettled(dbUpdates);

  // Send response, at this point all DB updates are done
};
Maxim Orlov
  • 1,942
  • 15
  • 18
0

I think an easy answer would be hiding the for loop in an async function, then use it in the endpoint.

const updateUserField = async () =>
  db.collection("users").updateOne(
    {
      username: req.query.username,
    },
    {
      $set: { [key]: req.body[key] },
    }
  );


const updateUserEndpoint = (req, res) => {
   for (let field of req.body) {
      await updateUserField(field);
   }

   res.status(200).send('updated');
}

Then, for testing, you could wrap the endpoint with a function that injects the function itself:

const updateUserEndpoint = (userUpdateFunc) => userUpdateFunc(req, res);

This pattern is called dependency injection - when writing unit tests for the endpoint, you'd just replace userUpdateFunc with whatever mocked function you want to use. This removes the need to monkeypatch the function in the test.

Terreras
  • 26
  • 1
  • 4
  • The `updateUserField()` function is redundant since `updateOne()` already returns a promise. Also `updateUserField` is a `() => {}` which takes no arguments but you call it as `updateUserField(field)` it should either be `updateUserField()` (remove the `field`) or `updateUserField = (field) => {}` – slebetman Mar 23 '22 at 02:13
  • You forgot to mark `updateUserEndpoint` as `async`. Also inside `updateUserField` you're not returning the DB update so there's nothing to await really. The function also doesn't use the field argument. This solution, as it is, doesn't work. A better solution is to run the updates in parallel, as posted in my answer below. – Maxim Orlov Mar 24 '22 at 16:53