Tips for achieving asynchronous behavior using nested iterations in Firebase's forEach function

I have a collection of items, with each item linked to multiple transaction records.

- items
   - itemA
      - transactionA
      - transactionB
   - itemB
      - transactionC
      - transactionD

In my express application, I need to create an endpoint that retrieves all items along with their associated transactions as shown above.

The challenge lies in synchronizing Firebase with async-await. I attempted to utilize Promise.all() but encountered difficulties with nested operations:

let items = await db
      .collection(collections.items)
      .where("active", "==", true)
      .get()
      .then(async (snapshot) => {
        let itemList = [];
        snapshot.forEach(async (doc) => {
          let transactions = await doc.ref
            .collection("transactions")
            .get()
            .then(async (transactionSnapshot) => {
              let promises = [];
              transactionSnapshot.forEach(async (transactionDoc) => {
                promises.push(transactionDoc.data());
              });
              return Promise.all(promises);
            });
          functions.logger.log("transactions", transactions);
          itemList.push({ data: doc, transactions });
        });
        return Promise.all(itemList);
      });
    functions.logger.log("items", items);
    res.status(BASIC_HTTP_STATUS_CODES.success).json({ items });

Upon reviewing the logs, I noticed that some transactions are logged after the items, and the returned object is empty.

The code works fine when querying only the items without their transactions.

How can I ensure that the program waits for all item documents to finish attaching their respective transaction details before proceeding?

Answer №1

The issue lies specifically within this line of code:

Promise.all(prods);

In this case, the variable prods is structured as a

({ data: QueryDocumentSnapshot, prices: DocumentData[] })[]
, instead of an array of Promises. This discrepancy results in varying outcomes depending on how Promise.all() is utilized.

To address this, it is necessary for prods to be defined as a

Promise<{ data: QueryDocumentSnapshot, prices: DocumentData[] }>[]
.

When attempting to parallelize your code in such a manner, I would suggest avoiding the use of async/await as it can introduce unexpected behaviors and is often unnecessary.

let products = await db
  .collection(collections.products)
  .where("active", "==", true)
  .get()
  .then((activeProductsQSnap) => { // QSnap = QuerySnapshot
    const fetchProductAndPricesPromises = [];
    
    activeProductsQSnap.forEach((productDoc) => {
    
      const thisProductPromise = productDoc.ref
        .collection("prices")
        .get()
        .then((productPricesQSnap) => {
          const prices = [];
          productPricesQSnap.forEach((priceDoc) => {
            prices.push(priceDoc.data());
          });
          
          functions.logger.log("prices for product #" + productDoc.id, prices);
          
          return { data: productDoc.data(), prices };
        });
      
      fetchProductAndPricesPromises.push(thisProductPromise);
    });
    
    return Promise.all(fetchProductAndPricesPromises);
  });

functions.logger.log("products", products);
res.status(BASIC_HTTP_STATUS_CODES.success).json({ products });

Below is the revised version of the same code utilizing the async/await syntax:

async function getPricesForProductDoc(productDoc) {
  const productPricesQSnap = await productDoc.ref
    .collection("prices")
    .get();
  
  const prices = [];
  productPricesQSnap.forEach((priceDoc) => {
    prices.push(priceDoc.data());
  });
  
  functions.logger.log("prices for product #" + productDoc.id, prices);
  
  return { data: productDoc.data(), prices };
}

const activeProductsQSnap = await db
  .collection(collections.products)
  .where("active", "==", true)
  .get();

const promises = [];

activeProductsQSnap.forEach(
  (productDoc) => promises.push(getPricesForProductDoc(productDoc))
);

const products = await Promise.all(promises);

functions.logger.log("products", products);
res.status(BASIC_HTTP_STATUS_CODES.success).json({ products });

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Retrieving the return value from an AJAX call in C# using asynchronous methods

When it comes to retrieving a value using Ajax on the client side, I rely on this JQuery function: $.ajax({ type: "POST", url: "/my-page.aspx/checkout", contentType: "application/json; charset=utf-8", dataType: "json", success: functio ...

Having trouble persisting data with indexedDB

Hi there, I've encountered an issue with indexedDB. Whenever I attempt to store an array of links, the process fails without any visible errors or exceptions. I have two code snippets. The first one works perfectly: export const IndexedDB = { initDB ...

`Implementing Typescript code with Relay (Importing with System.js)`

Is there a way to resolve the error by including system.js or are there alternative solutions available? I recently downloaded the relay-starter-kit (https://github.com/relayjs/relay-starter-kit) and made changes to database.js, converting it into databas ...

What is the best way to place a popover above a selected text section?

Imagine a situation where a body of text is present. When a word is highlighted within this text, a popover should appear with a tooltip positioned directly at the highlighted word. Think of it as similar to how a Mac displays definitions of words within ...

Transferring a basic PHP date variable to JavaScript

Here is my PHP code snippet: <?php $mytestdate = "Oct 23, 2019 20:00:00"; ?> I'm facing an issue with this JavaScript code: <script> var mydate = "<?php echo $mytestdate; ?>"; // Set the date we're counting down to var countD ...

Navigating between components using AngularJS and ExpressJS

Currently, I am embarking on a project that involves utilizing express and angularjs. To guide me through this process, I have been referring to this particular tutorial. Upon my initial attempt of running localhost:3000, I successfully loaded my index.jad ...

How to break down JSON into individual elements using JavaScript

{ "name": "Sophia", "age": "Unknown", "heroes": ["Batman", "Superman", "Wonder Woman"], "sidekicks": [ { "name": "Robin" }, { "name": "Flash Gordon" }, { "name": "Bucky Barnes" } ...

The issue of Basic Bootstrap 5 Modal triggering twice is causing a frustrating experience

My modal is almost working perfectly - it changes content based on the clicked image, but it is triggering twice in the backend and I can't figure out why! I followed Bootstrap's documentation for this, so I'm unsure where the issue lies. Al ...

Tips for closing a MUI Modal that was opened from a parent component within a child component in React

Currently, I am in the process of constructing a modal component that can be triggered from a parent component and closed from within the modal itself using React. To achieve this functionality, I have initialized the state in the parent component and the ...

Determine the hour difference between two provided dates by utilizing the date-fns library

My API returns a "datePublished" timestamp like this: "2019-11-14T14:54:00.0000000Z". I am attempting to calculate the difference in hours between this timestamp and the current time using date.now() or new Date(). I am utilizing the date-fns v2 library fo ...

Integrating AngularJS code into dynamically generated HTML using JavaScript

I am currently utilizing an outdated version of AngularJS (1.3). I have a page where I need to dynamically display different content based on database values. If the user interacts with the page and changes the database value, I want the displayed content ...

Is the handlebars template to be rendered if the field is equal to the variable stored in req

Let's break it down: {#if creatorField == req.locals.user) <div class="thisHTML">{{ story }}</div> {else} <div class="thatHTML">{{ story }}</div> {/if} In case the creatorField doesn't match the value of req.locals.u ...

Kindly utilize the POST method for your request

After running the script below, I encountered an error message stating "Please use POST request". As a beginner in HTML and JavaScript, I am unsure of what is causing this issue. Can anyone provide guidance on what may be wrong? Javascript function subm ...

Is there a more productive approach to creating numerous React components?

I am currently working on a page where I need to render 8 components. The only thing that differs between each component is the values that are being iterated... <Item classModifier="step" image={Step1} heading={Copy.one.heading} /> <Item ...

Create a focal point by placing an image in the center of a frame

How can an image be placed and styled within a div of arbitrary aspect ratio to ensure it is inscribed and centered, while maintaining its position when the frame is scaled? Ensure the image is both inscribed and centered Set dimensions and position usin ...

AngularJS Event Handler Fails to Trigger

I'm currently working on a form that I need to submit using the ng-submit event through a custom Auth service. This is a snippet of the login.html (partial template) <div class='container'> <form class='form-signin' ...

What steps can be taken to obtain the fully computed HTML rather than the source HTML?

Is there a way to access the final computed HTML of a webpage that heavily relies on javascript to generate its content, rather than just the source HTML? For example, if a page contains script functions that dynamically generate HTML, viewing the page sou ...

Tips for maintaining a healthy balance of tasks in libuv during IO operations

Utilizing Typescript and libuv for IO operations is crucial. In my current situation, I am generating a fingerprint hash of a particular file. Let's say the input file size is approximately 1TB. To obtain the file's fingerprint, one method involv ...

Iterating through elements to set the width of a container

I am currently working on a function that dynamically adjusts the width of an element using JavaScript. Here is my JavaScript code: <script> $('.progress-fill span').each(function(){ var percent = $(this).html(); if(per ...

Angular 1.5's Ui-Router 1.0.0-beta.3 is missing a template configuration for 'resolve@root'

Struggling to make UI-Router's resolve method function properly, I'm encountering an error that I haven't been able to resolve. Error: No template configuration specified for 'resolve@root' Here is the definition of my stateProvid ...