首页 > 解决方案 > 为什么我的异步函数总是返回未定义?

问题描述

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

这是我正在等待的功能:

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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,如果没有,那么我返回项目数组,所以贸易可以继续。

编辑:我试图重新调整它,它只工作了一半。顶级看看我正在尝试做的事情:

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。False 是我期望的正确值,但我怎样才能访问它?它的形式是一个承诺对象?

标签: javascriptreactjsfirebasegoogle-cloud-firestoreasync-await

解决方案


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

变量注释

如果您没有使用 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)

使用 justdoc时,您需要查看它用于上下文的位置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但不完全是)。这些更安全,可以防止意外覆盖不应该覆盖的内容。

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

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);

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

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

关于 Promise 的注释

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`

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

要以原子方式写入数据库,您需要使用批处理写入,将一堆更改捆绑在一起,然后将它们发送到 Firestore。如果其中任何一个失败,则不会更改数据库中的数据。

接下来,您将用户的通知存储在他们的用户文档中。对于少量通知,这很好,但如果您只想提取地址或电话号码,如上一节中的示例,您是否需要下载所有这些通知?我建议将它们分成自己的文档(例如/users/{someUserId}/metadata/notifications),但最好是自己的集合(例如/users/{someUserId}/notifications/{someNotificationID})。通过将它们放在自己的集合中,您可以查询它们并用于QuerySnapshot#docChanges同步更改并使用 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
}

推荐阅读