0

i'm newbie and I'm trying to learn more from firebase function From my client side I created an order with an header and an array contains all item selected. For a firebase function trigger I want to read all item selected and update a counter into warehouse table. My problem is that item must be present more than once so I need to perform operation in sequential mode. Here my code :

exports.writeToFirestore = functions.firestore
    .document('/magazzino_esterni_2022/{esternoId}')
    .onCreate((snap, context) => {

    let items = snap.data().magazzino;
    items.forEach((item) => {  
        console.log("start update", item)
        const itemDataRef = firestore.collection("magazzino_articoli_2022").doc('GIACCA_3XS');  // use fix document to test 
        itemDataRef.get().then((doc) => {  
            const new_count = doc.data().count +1;
            console.log("update to ", new_count)
            itemDataRef.update({
                count: Number(new_count) 
            }).then (() => {
                console.log("update completed")
            }) 
        })
        console.log("end update", item)
    });
});

and here the log :

as you see, second item cannot see the incement done by first item

danh
  • 62,181
  • 10
  • 95
  • 136
Giulio
  • 1

2 Answers2

0

If you want to update the counter synchronously you can't use a forEach loop. The best way to do it is a for of loop.

Bergi's answer here can help you: Using async/await with a forEach loop

Jake
  • 1
0

Using the OP's .then() style, gather the get/update promises and execute them together with Promise.all().

exports.writeToFirestore = functions.firestore
    .document('/magazzino_esterni_2022/{esternoId}')
    .onCreate((snap, context) => {

  let items = snap.data().magazzino;
  let promises = items.map(item => {  
    console.log("start update", item);
    const itemName = item.data().name;  // OP must check this.. use whatever data prop contains 'GIACCA_3XS'
    return updateMagazineCount(itemName);
  });
  // here's the punch line: run the promises concurrently before finishing
  return Promise.all(promises).then(() => {
    console.log("end update")
  });
});

// for clarity, a separate function that gets and updates
function updateMagazineCount(itemName) {
  const itemDataRef = firestore.collection("magazzino_articoli_2022").doc(itemName);
  return itemDataRef.get().then(doc => {  
    const count = doc.data().count + 1;
    console.log("update to ", count);
    return itemDataRef.update({ count });
  }).then(result => {
    console.log("update completed");
    return result;
  });
}

If you prefer the newer async / await style, you should still gather promises in a collection and use Promise.all(). (Don't await them in a loop unless they must be performed sequentially).

exports.writeToFirestore = functions.firestore
    .document('/magazzino_esterni_2022/{esternoId}')
    .onCreate(async (snap, context) => {

  let items = snap.data().magazzino;
  let promises = items.map(item => {  
    console.log("start update", item);
    const itemName = item.data().name;  // OP must check this.. use whatever data prop contains 'GIACCA_3XS'
    return updateMagazineCount(itemName);
  });
  await Promise.all(promises)
  console.log("end update")
});

async function updateMagazineCount(itemName) {
  const itemDataRef = firestore.collection("magazzino_articoli_2022").doc(itemName);
  const doc = await itemDataRef.get()
  const count = doc.data().count + 1;
  console.log("update to ", count);
  const result = await itemDataRef.update({ count });
  console.log("update completed");
  return result;
}

To perform the get/updates sequentially, build a chain of promises with then, or in the newer style, you would await in a loop.

// then style
exports.writeToFirestore = functions.firestore
    .document('/magazzino_esterni_2022/{esternoId}')
    .onCreate(async (snap, context) => {

  let items = snap.data().magazzino;
  let promise = Promise.resolve();
  for (let item of items) { 
    console.log("start update", item);
    const itemName = item.data().name;  // OP must check this.. use whatever data prop contains 'GIACCA_3XS'
    promise = promise.then(() => updateMagazineCount(itemName));
  }

  return promise.then(() => {
    console.log("end update")
  });
});

Or using async style...

exports.writeToFirestore = functions.firestore
  .document('/magazzino_esterni_2022/{esternoId}')
  .onCreate(async (snap, context) => {

  let items = snap.data().magazzino;

  for (let item of items) {
    console.log("start update", item);
    const itemName = item.data().name;  // OP must check this.. use whatever data prop contains 'GIACCA_3XS'
    await updateMagazineCount(itemName);
  }
  console.log("end update", item)
});
danh
  • 62,181
  • 10
  • 95
  • 136
  • thanks for the answer. If I understood correctly your example, all updates are made in async mode and using promise.all I have guarantee me that all updates occurs before function ended. My point is a bit different : I want to execute the second update only when first update is completed. – Giulio Jul 22 '22 at 20:37
  • Oh, I see. Did you see what I said in parens ("don't wait in the loop unless you want them to be performed sequentially"). I think you want them sequentially. I'll edit to demo both styles. – danh Jul 22 '22 at 23:10