1

I am working on a Firebase cloud function to update a field value for every element of the array I provide.

Below is my loop.

I would like for it to add a document to another collection as soon as this loop has been completed.

// FOR EACH ITEM
sortedArray.forEach(([key, val]) => {
    walletRef.doc('j10wmCUhUWPxYJpIElBxmFAEI6l1').update({
        [val.product]: admin.firestore.FieldValue.increment(val.quantity),
    })
})

When I try to add a .then like below, it fails

// FOR EACH ITEM
sortedArray.forEach(([key, val]) => {
    walletRef.doc('document_id_here').update({
        [val.product]: admin.firestore.FieldValue.increment(val.quantity),
    })
}).then((doc) => {
    logsRef.add({
        user_id: "document_id_here",
        product: items,
        transactionType: "purchase",
        date: new Date(),
    })
});

Logged error:

textPayload: "Function execution took 2594 ms, finished with status: 'crash'"

The field values are updated but the new document is not created. Please help. Thanks.

David
  • 473
  • 1
  • 4
  • 14
  • Why are you updating the same document in a loop? Can you not _reduce_ the `sortedArray` to a single object to update all properties at once? – Phil May 11 '23 at 08:12
  • The items in the sortedArray are dynamic and can change so there are no fixed fields. So when the field is missing, the loop creates it and updates it – David May 11 '23 at 08:13
  • Shouldn't be a problem. From [the docs](https://firebase.google.com/docs/firestore/manage-data/add-data#increment_a_numeric_value)... _"Note: If the field does not exist or if the current field value is not a numeric value, the operation sets the field to the given value"_ – Phil May 11 '23 at 08:17
  • The items are an array of objects [ { product: "productOne", quantity: 13,price: 123},...] which I then reduce to key value pairs, of productOne: 13, I didn't have a better way of handling it which is why I went for the loop which does the job of updating. But I get stuck with adding a .then method to it – David May 11 '23 at 08:20
  • If the forEach method returns the array of promises, then resolve that array of promises using Promise.resolve() method – Bennison J May 11 '23 at 11:17

2 Answers2

2

Your update loop seems unnecessary since you're performing the operations on the same document.

Try something like this instead, setting multiple properties at once

walletRef
  .doc("document_id_here")
  .update(
    Object.fromEntries(
      sortedArray.map(({ product, quantity }) => [
        product,
        admin.firestore.FieldValue.increment(quantity),
      ])
    )
  )
  .then((writeResult) => {
    // ...
  });

From the docs for Increment a numeric value...

★ Note: If the field does not exist or if the current field value is not a numeric value, the operation sets the field to the given value

Phil
  • 157,677
  • 23
  • 242
  • 245
  • 1
    @David you're most welcome. This will also result in fewer document writes and might save you a little in Firebase costs – Phil May 11 '23 at 08:46
2

Depending on your requiremenents there are different ways to loop with promises

The first one is to use Promise.all and .map:

Promise.all(
    sortedArray.map(item => {
        return do_something_that_returns_promise()
            .then(effect_that_also_return_promise);
    })
).then(afterAllPromisesResolved);

afterAllPromisesResolved - will receive an array of values that are returned resolved in promises.

From docs:

The Promise.all() static method takes an iterable of promises as input and returns a single Promise. This returned promise fulfills when all of the input's promises fulfill (including when an empty iterable is passed), with an array of the fulfillment values. It rejects when any of the input's promises rejects, with this first rejection reason.

Important to note: Promise.all will run promises all together at the same time, so if you have 1000 async http request it may face ratelimits

To avoid rate limits you can use sequential way with for await:

(async ()=> {
  const results = [];
  for (const item of sortedArray){
     const result1 = await do_something_that_returns_promise();
     results.append(await effect_that_also_return_promise(result1));
  }
  await afterAllPromisesResolved(results)
})().then(afterAbsolutelyAllPromisesResolved);

The difference is in the way the promises will be resolved. In Promise.all approach your promises will run in "concurrent" way. In for await approach the promises will be resolved in sequential fashion

Mr Davron
  • 97
  • 1
  • 7