为什么我的异步函数总是返回 undefined?

时间:2021-05-10 18:43:12

标签: javascript reactjs firebase google-cloud-firestore async-await

似乎我使用异步错误,有人能发现我做错了什么吗?

这是我正在等待的功能:

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
export async function firebaseAcceptTradeOffer(tradeOfferID, userData) {
  var tradeInstanceID;
  var senderID;
  var receiverID;
  var senderItemsTemp;
  var receiverItemsTemp;
  var response;
  var tradeOffer = db.collection("tradeOffers").doc(tradeOfferID);
  return tradeOffer
    .get()
    .then((doc) => {
      senderItemsTemp = doc.data().sendersItems;
      receiverItemsTemp = doc.data().receiversItems;
      senderID = doc.data().senderID;
      receiverID = doc.data().receiverID;
    })
    .then(() => {
      var itemInTrade = false;
      senderItemsTemp.forEach((item) => {
        db.collection("listings")
          .doc(item.itemID)
          .get()
          .then((doc) => {
            if (doc.data().status !== "listed") {
              itemInTrade = true;
            }
          })
          .then(() => {
            receiverItemsTemp.forEach((item) => {
              db.collection("listings")
                .doc(item.itemID)
                .get()
                .then((doc) => {
                  if (doc.data().status !== "listed") {
                    itemInTrade = true;
                  }
                })
                .then(() => {
                  if (itemInTrade) {
                    tradeOffer.update({
                      status: "declined",
                    });
                    return false;
                  } else {
                    db.collection("trades")
                      .add({
                        tradeOfferID: tradeOfferID,
                        senderTradeStatus: {
                          created: true,
                          sentToSeekio: "current",
                          inspection: false,
                          sentToPartner: false,
                        },
                        receiverTradeStatus: {
                          created: true,
                          sentToSeekio: "current",
                          inspection: false,
                          sentToPartner: false,
                        },
                        postagePhotos: [],
                        inspectionPhotos: [],
                        senderPaid: false,
                        receiverPaid: false,
                        senderUploadedProof: false,
                        receiverUploadedProof: false,
                        senderID: senderID,
                        receiverID: receiverID,
                        messages: [
                          {
                            message: `Trade created. A representative, will message this chat shortly with instructions and postage address. If you would like more information about the trading process, head to seekio.io/help. Thank you for using Seekio!`,
                            sender: "System",
                            timestamp: firebase.firestore.Timestamp.fromDate(
                              new Date()
                            ),
                          },
                        ],
                      })
                      .then((docRef) => {
                        tradeInstanceID = docRef.id;
                        tradeOffer
                          .set(
                            {
                              status: "accepted",
                              tradeInstanceID: docRef.id,
                            },
                            { merge: true }
                          )
                          .then(() => {
                            var receiver = db.collection("users").doc(senderID);
                            var notification = {
                              from: auth.currentUser.uid,
                              fromUsername: userData.username,
                              type: "tradeOfferAccepted",
                              time: firebase.firestore.Timestamp.fromDate(
                                new Date()
                              ),
                              seen: false,
                            };
                            receiver
                              .update({
                                notifications: firebase.firestore.FieldValue.arrayUnion(
                                  notification
                                ),
                              })
                              .then(() => {
                                response = {
                                  sendersItems: senderItemsTemp,
                                  receiversItems: receiverItemsTemp,
                                };
                                return response;
                              });
                          });
                      })
                      .catch((err) => console.log(err));
                  }
                });
            });
          });
      });
    });
}

这里是我调用它的地方:

  //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  async function acceptTradeOffer() {
    var tradeOfferID = currentTradeFocus;
    var senderID = "";
    setLoading("loading");
    if (userData !== null && tradeOfferID !== "") {
      const response = await firebaseAcceptTradeOffer(
        currentTradeFocus,
        userData
      );
      console.log(
        "RESPONSE FROM FIREBASE SERVICE>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>: ",
        response
      );
      if (!response) {
        setErrorMsg("One of the selected items is no longer available.");
      } else if (
        response.sendersItems !== null &&
        response.receiversItems !== null
      ) {
        setSenderItems(response.sendersItems);
        setReceiverItems(response.receiversItems);
        toggleConfirmScreen("cancel");
        setLoading("idle");
        setItemsSet(true);
      }
      fetch(
        "https://europe-west2-seekio-86408.cloudfunctions.net/sendMail?type=tradeUpdate&userID=" +
          senderID
      ).catch((err) => {
        console.log(err);
        setLoading("idle");
      });
    }
  }

所以基本上我想检查一下这个“交易”中的任何物品是否不等于“列出”(这意味着它们不可用,我想返回 false,如果不是,那么我返回项目数组,以便交易可以继续。

EDIT:我已经尝试重新调整它,但它已经成功了一半。对我正在尝试做的事情的顶级看法:

User wants to accept a trade offer for some items >
Check through all items to make sure they are available and not sold >
If so, accept the trade >
Then once its accepted, go and cancel all remaining trade offers that include items from this accepted trade, cause they are not available anymore.


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
export async function firebaseAcceptTradeOffer(tradeOfferID, userData) {
  console.log(
    "----- starting firebaseAcceptTradeOffer--------- ",
    unavailableItem
  );
  //==============
  var tradeInstanceID;
  var senderID;
  var receiverID;
  var senderItemsTemp;
  var receiverItemsTemp;
  var unavailableItem = false;
  var response;
  var itemsArray;
  var notListed = false;
  //==============
  var tradeOffer = db.collection("tradeOffers").doc(tradeOfferID);

  unavailableItem = tradeOffer
    .get()
    .then((doc) => {
      senderID = doc.data().senderID;
      receiverID = doc.data().receiverID;
      itemsArray = doc.data().sendersItems.concat(doc.data().receiversItems);
    })
    .then(() => {
      itemsArray.forEach((item) => {
        db.collection("listings")
          .doc(item.itemID)
          .get()
          .then((doc) => {
            if (doc.data().status !== "listed") {
              notListed = true;
            }
          });
      });
    })
    .then(() => {
      return notListed;
    });

  console.log(
    "-----unavailableItem at the end of method --------- ",
    unavailableItem
  );

  //^^^^^^^^^^ here i am getting a promise result of false (which is correct) but HOW CAN I ACCESS IT

  if (unavailableItem) {
    tradeOffer.update({
      status: "declined",
    });
    return false;
  } else {
    response = await createTrade(
      tradeOffer,
      tradeOfferID,
      senderID,
      receiverID,
      userData.username
    );
    console.log("response from createTrade", response);
    return response;
  }
}

我得到了一个带有上面值 false 的 promise 对象。 False 是我期望的正确值,但我如何访问它?它以承诺对象的形式出现?

2 个答案:

答案 0 :(得分:2)

我手头有一些时间,所以让我们分解一下。

关于变量的说明

如果您不使用 TypeScript(即使您使用),我强烈建议您将类型插入到您的变量名称中。

db                # ✔ by convention, either firebase.database() or firebase.firestore()
tradeOffer        # ❓ type unclear, could be a number, an object, a string, etc
tradeOfferDocRef  # ✔ a DocumentReference
trades            # ❓ type unclear, plural implies a collection of some sort
tradesColRef      # ✔ a CollectionReference

您可能还会遇到这些:

doc               # ❓ by convention, a DocumentSnapshot, but with unknown data
tradeDoc          # ✔ implies a DocumentSnapshot<TradeData> (DocumentSnapshot containing trade data)

仅使用 doc 时,您需要查看它用于此 DocumentSnapshot 所包含内容的上下文的位置。

db.collection('trades').doc(tradeOfferID).get()
  .then((doc) => { // contents implied to be TradeData
    const data = doc.data();
  });
// or
tradeDocRef.get()
  .then((doc) => { // contents implied to be TradeData
    const data = doc.data();
  });

您应该根据需要重命名 doc,尤其是在使用 async/await 语法时,以免出现以下情况:

const doc = await db.collection('trades').doc(tradeOfferID).get();
/* ... many lines ... */
const senderID = doc.get("senderID"); // what was doc again?

由于您在问题中标记了 reactjs,这意味着您使用的是现代 JavaScript。

放弃对 var 的任何使用并将其替换为块范围版本:const(防止重新分配变量)或 let(类似于 var、{{ 3}})。这些更安全,并防止意外覆盖您不应该覆盖的内容。

您还可以使用对象解构来分配变量。

const senderID = doc.data().senderID;
const receiverID = doc.data().receiverID;
const itemsArray = doc.data().sendersItems.concat(doc.data().receiversItems);

可以变成:

const { senderID, receiverID, sendersItems, receiversItems } = doc.data();
const itemsArray = sendersItems.concat(receiversItems);

如果您只需要文档中的一个属性,您应该使用 but not quite 而不是 DocumentSnapshot#get(),这样它只会解析您想要的字段而不是整个文档的数据。

function getUserAddress(uid) {
  return firebase.firestore()
    .collection('users')
    .doc(uid)
    .get()
    .then(userDoc => userDoc.get("address")); // skips username, email, phone, etc
}

关于承诺的说明

var senderID;
var receiverID;
var itemsArray;

tradeOfferDocRef
  .get()
  .then((doc) => {
    senderID = doc.data().senderID;
    receiverID = doc.data().receiverID;
    itemsArray = doc.data().sendersItems.concat(doc.data().receiversItems);
  })
  .then(() => {
    /* use results from above */
  });

虽然上述代码块按预期运行,但当您像这样拥有许多这样的变量时,就不清楚它们是在何时何地设置的。

这也会导致您认为变量具有值的问题:

var senderID;
var receiverID;
var itemsArray;

tradeOfferDocRef
  .get()
  .then((doc) => {
    // this line runs after the line below
    senderID = doc.data().senderID;
    receiverID = doc.data().receiverID;
    itemsArray = doc.data().sendersItems.concat(doc.data().receiversItems);
  });

// this line before the line above
console.log(senderID); // will always log "undefined"

这可以通过以下三种方式之一避免:

  • 返回数据以传递给下一个处理程序(您不会在本示例中使用此方法,仅当下一个 then() 处理程序在别处时):
tradeOfferDocRef
  .get()
  .then((doc) => {
    const { senderID, receiverID, sendersItems, receiversItems } = doc.data();
    const itemsArray = sendersItems.concat(receiversItems);
    return { senderID, receiverID, itemsArray }; // pass to next step
  })
  .then((neededData) =>
    /* use neededData.senderID, neededData.receiverID, etc */
  });
  • 在同一处理程序中使用数据:
tradeOfferDocRef
  .get()
  .then((doc) => {
    const { senderID, receiverID, sendersItems, receiversItems } = doc.data();
    const itemsArray = sendersItems.concat(receiversItems);

    /* use results from above */
  });
  • 使用 async-await 语法:
const tradeDoc = await tradeOfferDocRef.get();

const { senderID, receiverID, sendersItems, receiversItems } = tradeDoc.data();
const itemsArray = sendersItems.concat(receiversItems);

/* use results from above */

写入 Firestore

您当前的代码包含以下步骤:

1. Get the trade offer document</li>
2. If successful, pull out the sender and receiver's IDs, along with any items in the trade
3. If successful, do the following for each item in the sender items array:
  a) Check if any of the sender's items are unavailable</li>
  b) If successful, do the following for each item in the receiver items array:
    - If **any item** was unavailable prior to this, decline the trade & return `false`.
    - If all items **so far** are available, do the following:
      a) Create a document containing information about the trade with the needed data
      b) If successful, edit the trade offer document to accept it
      c) If successful, create a notification for the receiver
      d) If successful, return the traded items
      e) If any of a) to d) fail, log the error and return `undefined` instead
4. Return `undefined`

在上面的步骤中,您可以看到您的承诺链接存在一些问题。但除此之外,您还可以看到您是一个一个地创建和编辑文档,而不是一次(“原子地”)。如果这些写入中的任何一个失败,您的数据库最终将处于未知状态。例如,您可以创建并接受交易,但未能创建通知。

要以原子方式写入数据库,您需要使用 DocumentSnapshot#data() 将一堆更改捆绑在一起,然后将它们发送到 Firestore。如果其中任何一个失败,数据库中的数据不会发生变化。

接下来,您将用户的通知存储在他们的用户文档中。对于少量通知,这很好,但是如果您只想提取地址或电话号码(如上一节中的示例),是否需要下载所有这些通知?我建议将它们拆分为它们自己的文档(例如 /users/{someUserId}/metadata/notifications),但最好是它们自己的集合(例如 /users/{someUserId}/notifications/{someNotificationID})。通过将它们放在自己的集合中,您可以查询它们并使用 batched write 同步更改并使用 Cloud Firestore 触发器发送推送通知。

重构函数

1. Get the trade offer document</li>
2. Once the retrieved, do the following depending on the result:
  - If failed or empty, return an error
  - If successful, do the following:
    a) Pull out the sender and receiver's IDs, along with any items in the trade.
    b) For each item in the trade, check if any are unavailable and once the check has completed, do the following depending on the result:
      - If any item is unavailable, do the following:
        a) Decline the trade
        b) Return the list of unavailable items
      - If all items are available, do the following:
        a) Create a new write batch containing:
          - Create a document about the trade
          - Edit the trade offer document to accept it
          - Create a notification for the receiver
        b) Commit the write batch to Firestore
        c) Once the commit has completed, do the following depending on the result:
          - If failed, return an error
          - If successful, return the traded items and the trade's ID

因为这里的步骤相互依赖,所以这是使用 async/await 语法的一个很好的候选。

要查看实际效果,请仔细研究:

import * as firebase from "firebase-admin";

// insert here: https://gist.github.com/samthecodingman/aea3bc9481bbab0a7fbc72069940e527

async function firebaseAcceptTradeOffer(tradeOfferID, userData) {
  const tradeOfferDocRef = db.collection("tradeOffers").doc(tradeOfferID);

  const tradeDoc = await tradeOfferDocRef.get();

  const { senderID, receiverID, sendersItems, receiversItems } =
    tradeDoc.data();
  const itemsArray = sendersItems.concat(receiversItems);

  // TODO: Check if this is an accurate assumption
  if (sendersItems.length == 0 || receiversItems.length == 0) {
    success: false,
    message: "One-sided trades are not permitted",
    detail: {
      sendersItemsIDs: sendersItems.map(({ itemID }) => itemID),
      receiversItemsIDs: receiversItems.map(({ itemID }) => itemID),
    },
  };

  const listingsColQuery = db
    .collection("listings")
    .where("status", "==", "listed");

  const uniqueItemIds = Array.from(
    itemsArray.reduce(
      (set, { itemID }) => set.add(itemID),
      new Set()
    )
  );

  const foundIds = {};

  await fetchDocumentsWithId(
    listingsColQuery,
    uniqueItemIds,
    (listingDoc) => {
      // if here, listingDoc must exist because we used .where("status") above
      foundIds[listingDoc.id] = true;
    }
  );

  const unavailableItemIDs = uniqueItemIds
    .filter(id => !foundIds[id]);

  if (unavailableItems.length > 0) {
    // one or more items are unavailable!
    await tradeOfferDocRef.update({
      status: "declined",
    });
    return {
      success: false,
      message: "Some items were unavailable",
      detail: {
        unavailableItemIDs,
      },
    };
  }

  const tradeDocRef = db.collection("trades").doc();
  const tradeInstanceID = tradeDocRef.id;

  const batch = db.batch();

  batch.set(tradeDocRef, {
    tradeOfferID,
    senderTradeStatus: {
      created: true,
      sentToSeekio: "current",
      inspection: false,
      sentToPartner: false,
    },
    receiverTradeStatus: {
      created: true,
      sentToSeekio: "current",
      inspection: false,
      sentToPartner: false,
    },
    postagePhotos: [],
    inspectionPhotos: [],
    senderPaid: false,
    receiverPaid: false,
    senderUploadedProof: false,
    receiverUploadedProof: false,
    senderID,
    receiverID,
    messages: [
      {
        message: `Trade created. A representative, will message this chat shortly with instructions and postage address. If you would like more information about the trading process, head to seekio.io/help. Thank you for using Seekio!`,
        sender: "System",
        timestamp: firebase.firestore.Timestamp.fromDate(new Date()),
      },
    ],
  });

  batch.set(
    tradeOfferDocRef,
    {
      status: "accepted",
      tradeInstanceID,
    },
    { merge: true }
  );

  const receiverNotificationRef = db
    .collection("users")
    .doc(senderID)
    .collection("notifications")
    .doc();

  batch.set(receiverNotificationRef, {
    from: auth.currentUser.uid,
    fromUsername: userData.username,
    type: "tradeOfferAccepted",
    time: firebase.firestore.Timestamp.fromDate(new Date()),
    seen: false,
  });

  await batch.commit();

  return {
    success: true,
    message: "Trade accepted",
    detail: {
      tradeID: tradeInstanceID,
      senderItems,
      receiversItems,
    },
  };
}

用法:

try {
  const tradeResult = await firebaseAcceptTradeOffer(someTradeId);
} catch (err) {
  // if here, one of the following things happened:
  //  - syntax error
  //  - database read/write error
  //  - database rejected batch write
}

答案 1 :(得分:1)

一般来说,当你返回一个无法解决的承诺时,你必须等待它的结果。此外,您必须从 promise then 链中返回一个值,至少最后一个 .then() 需要返回一个值,这也可以在 .finally() 方法中完成。< /p>

从任何 firebase 资源中使用 Get,实时、firestore 和存储都是异步进程,必须等待。在您的情况下,您缺少等待返回:

 var tradeOffer = db.collection("tradeOffers").doc(tradeOfferID);
  return tradeOffer

并且您似乎没有在 .then() 语句中返回任何内容,我建议您完全重写您正在尝试的内容,以便您根据需要返回值。