简体   繁体   中英

What is going wrong with my express call? I need an array of ID's but its returning an empty array

Im guessing this problem is because I don't know how to use async await effectively. I still dont get it and I've been trying to understand for ages. sigh.

Anyway, heres my function:

app.post("/declineTrades", async (request, response) => {
  //---------------------------------------------
  const batch = db.batch();
  const listingID = request.body.listingID;
  const tradeOfferQuery = db
    //---------------------------------------------
    //Get trade offers that contain the item that just sold
    //(therefore it cannot be traded anymore, I need to cancel all existing trade offers that contain the item because this item isn't available anymore)
    //---------------------------------------------
    .collection("tradeOffers")
    .where("status", "==", "pending")
    .where("itemIds", "array-contains", listingID);
  //---------------------------------------------
  //Function that gets all trade offers that contain the ID of the item.
  async function getIdsToDecline() {
    let tempArray = [];
    tradeOfferQuery.get().then((querySnapshot) => {
      querySnapshot.forEach((doc) => {
        //For each trade offer found
        let offerRef = db.collection("tradeOffers").doc(doc.id);
        //Change the status to declined
        batch.update(offerRef, { status: "declined" });
        //Get the data from the trade offer because I want to send an email 
        //to the  who just got their trade offer declined.
        const offerGet = offerRef.get().then((offer) => {
          const offerData = offer.data();
          //Check the items that the receiving person had in this trade offer
          const receiverItemIds = Array.from(
            offerData.receiversItems
              .reduce((set, { itemID }) => set.add(itemID), new Set())
              .values()
          );
          //if the receiver item id's array includes this item that just sold, I know that
          //I can get the sender ID (users can be sender or receiver, so i need to check which person is which)
          if (receiverItemIds.includes(listingID)) {
            tempArray.push(offerData.senderID);
          }
        });
      });
    });
    //With the ID's now pushed, return the tempArray
    return tempArray;
  }
  //---------------------------------------------
  //Call the above function to get the ID's of people that got declined 
  //due to the item no longer being available
  const peopleToDeclineArray = await getIdsToDecline();
  //Update the trade offer objects to declined
  const result = await batch.commit();
  //END
  response.status(201).send({
    success: true,
    result: result,
    idArray: peopleToDeclineArray,
  });
});

Im guessing that my return tempArray is in the wrong place? But I have tried putting it in other places and it still returns an empty array. Is my logic correct here? I need to run the forEach loop and add to the array before the batch.commit happens and before the response is sent.

TIA Guys!

As @jabaa pointed out in their comment , there are problems with an incorrectly chained Promise in your getIdsToDecline function.

Currently the function initializes an array called tempArray , starts executing the trade offer query and then returns the array (which is currently still empty) because the query hasn't finished yet.

While you could throw in await before tradeOfferQuery.get() , this won't solve your problem as it will only wait for the tradeOfferQuery to execute and the batch to be filled with entries, while still not waiting for any of the offerRef.get() calls to be completed to fill the tempArray .

To fix this, we need to make sure that all of the offerRef.get() calls finish first. To get all of these documents, you would use the following code to fetch each document, wait for all of them to complete and then pull out the snapshots:

const itemsToFetch = [ /* ... */ ];
const getAllItemsPromise = Promise.all(
  itemsToFetch.map(item => item.get())
);

const fetchedItemSnapshots = await getAllItemsPromise;

For documents based on a query, you'd tweak this to be:

const querySnapshot = /* ... */;

const getSenderDocPromises = [];
querySnapshot.forEach((doc) => {
  const senderID = doc.get("senderID");
  const senderRef = db.collection("users").doc(senderID);

  getSenderDocPromises.push(senderRef.get());
}

const getAllSenderDocPromise = Promise.all(getSenderDocPromises);

const fetchedSenderDataSnapshots = await getAllSenderDocPromise;

However neither of these approaches are necessary, as the document you are requesting using these offerRef.get() calls are already returned in your query so we don't even need to use get() here!

(doc) => {
  let offerRef = db.collection("tradeOffers").doc(doc.id);
  //Change the status to declined
  batch.update(offerRef, { status: "declined" });
  //Get the data from the trade offer because I want to send an email 
  //to the  who just got their trade offer declined.
  const offerGet = offerRef.get().then((offer) => {
    const offerData = offer.data();
    //Check the items that the receiving person had in this trade offer
    const receiverItemIds = Array.from(
      offerData.receiversItems
        .reduce((set, { itemID }) => set.add(itemID), new Set())
        .values()
    );
    //if the receiver item id's array includes this item that just sold, I know that
    //I can get the sender ID (users can be sender or receiver, so i need to check which person is which)
    if (receiverItemIds.includes(listingID)) {
      tempArray.push(offerData.senderID);
    }
  });
}

could be replaced with just

(doc) => {
  // Change the status to declined
  batch.update(doc.ref, { status: "declined" });

  // Fetch the IDs of items that the receiving person had in this trade offer
  const receiverItemIds = Array.from(
    doc.get("receiversItems") // <-- this is the efficient form of doc.data().receiversItems
      .reduce((set, { itemID }) => set.add(itemID), new Set())
      .values()
  );

  // If the received item IDs includes the listed item, add the
  // sender's ID to the array
  if (receiverItemIds.includes(listingID)) {
    tempArray.push(doc.get("senderID"));
  }
}

which could be simplified to just

(doc) => {
  //Change the status to declined
  batch.update(doc.ref, { status: "declined" });

  // Check if any items that the receiving person had in this trade offer
  // include the listing ID.
  const receiversItemsHasListingID = doc.get("receiversItems")
    .some(item => item.itemID === listingID);

  // If the listing ID was found, add the sender's ID to the array
  if (receiversItemsHasListingID) {
    tempArray.push(doc.get("senderID"));
  }
}

Based on this, getIdsToDecline actually queues declining the invalid trades and returns the IDs of those senders affected. Instead of using the batch and tradeOfferQuery objects that are outside of the function that make this even more unclear, you should roll them into the function and pull it out of the express handler. I'll also rename it to declineInvalidTradesAndReturnAffectedSenders .

async function declineInvalidTradesAndReturnAffectedSenders(listingID) {
  const tradeOfferQuery = db
    .collection("tradeOffers")
    .where("status", "==", "pending")
    .where("itemIds", "array-contains", listingID);

  const batch = db.batch();
  const affectedSenderIDs = [];
  
  const querySnapshot = await tradeOfferQuery.get();

  querySnapshot.forEach((offerDoc) => {
    batch.update(offerDoc.ref, { status: "declined" });

    const receiversItemsHasListingID = offerDoc.get("receiversItems")
      .some(item => item.itemID === listingID);

    if (receiversItemsHasListingID) {
      affectedSenderIDs.push(offerDoc.get("senderID"));
    }
  }

  await batch.commit(); // generally, the return value of this isn't useful

  return affectedSenderIDs;
}

This then would change your route handler to:

app.post("/declineTrades", async (request, response) => {
  
  const listingID = request.body.listingID;
  
  const peopleToDeclineArray = await declineInvalidTradesAndReturnAffectedSenders(listingID);

  response.status(201).send({
    success: true,
    result: result,
    idArray: peopleToDeclineArray,
  });
});

Then adding the appropriate error handling, swapping out the incorrect use of HTTP 201 Created for HTTP 200 OK , and using json() instead of send() ; you now get:

app.post("/declineTrades", async (request, response) => {
  try {
    const listingID = request.body.listingID;
  
    const affectedSenderIDs = await declineInvalidTradesAndReturnAffectedSenders(listingID);

    response.status(200).json({
      success: true,
      idArray: affectedSenderIDs, // consider renaming to affectedSenderIDs
    });
  } catch (error) {
    console.error(`Failed to decline invalid trades for listing ${listingID}`, error);

    if (!response.headersSent) {
      response.status(500).json({
        success: false,
        errorCode: error.code || "unknown"
      });
    } else {
      response.end(); // forcefully end corrupt response
    }
  }
});

Note: Even after all these changes, you are still missing any form of authentication. Consider swapping the HTTPS Event Function out for a Callable Function where this is handled for you but requires using a Firebase Client SDK.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM