简体   繁体   English

为什么我的异步 function 总是返回未定义?

[英]Why does my async function always return undefined?

It seems im using async wrong, can anybody spot what I am doing wrong?似乎我使用异步错误,有人能发现我做错了什么吗?

This is the function I am waiting on:这是我正在等待的 function:

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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));
                  }
                });
            });
          });
      });
    });
}

And here is where I am calling it:这就是我所说的:

  //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  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");
      });
    }
  }

So basically I want to go and check if any of the items in this 'trade' are not equal to 'listed' (which means they are not available, I want to return false , if not, then I return the array of items so the trade can continue.所以基本上我想 go 并检查这个“交易”中的任何项目是否不等于“列出”(这意味着它们不可用,我想返回false ,如果没有,那么我返回项目数组所以交易可以继续。

EDIT : I've tried to rejig it all and it's half working.编辑:我试图重新调整它,它只工作了一半。 A top level look at what I am trying to do:顶级看看我正在尝试做的事情:

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

I am getting a promise object back with the value false above.我得到了一个 promise object ,上面的值是false False is correct value I am expecting, but how can I access it? False 是我期望的正确值,但我怎样才能访问它? its in the form of a promise object?它的形式是 promise object?

I have some time on my hands, so let's break this down.我手头有一些时间,所以让我们分解一下。

Notes on Variables变量注释

If you aren't using TypeScript (and even if you are), I highly recommend inserting the type into the name of your variables.如果您不使用 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

You may also encounter these:你可能还会遇到这些:

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

When using just doc , you need to look around where its used for context on what this DocumentSnapshot contains.仅使用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();
  });

You should rename doc as appropriate, especially when using async / await syntax, so you don't end up in situations like:您应该根据需要重命名doc ,尤其是在使用async / await语法时,因此您不会遇到以下情况:

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

As you've tagged reactjs in your question, this implies you are using modern JavaScript.正如您在问题中标记reactjs ,这意味着您使用的是现代 JavaScript。

Ditch any use of var and replace it with the block-scoped versions: const (prevents reassigning the variable) or let (similar to var , but not quite ).放弃任何使用var并将其替换为块范围的版本: const (防止重新分配变量)或let (类似于var但不完全)。 These are safer and prevents the chances of accidentally overwriting something you shouldn't.这些更安全,可以防止意外覆盖不应该覆盖的内容。

You can also make use of Object destructuring to assign your variables.您还可以使用 Object 解构来分配变量。

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

can become:可以变成:

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

If you ever need only a single property out of a document, you should use DocumentSnapshot#get() instead of DocumentSnapshot#data() so it will parse only the field you want instead of the whole document's data.如果您只需要文档中的单个属性,则应使用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
}

Notes on Promises关于 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 */
  });

While the above code block functions as intended, when you have many of these variables like this as you do, it becomes unclear when and where they are set.虽然上面的代码块按预期运行,但当您像这样拥有许多这样的变量时,它们的设置时间和位置就变得不清楚了。

It also leads to problems like this where you think the variable has a value:它还会导致这样的问题,您认为变量具有值:

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"

This can be avoided in one of three ways:这可以通过以下三种方式之一来避免:

  • Returning data to pass through to the next handler (you wouldn't use this for this example, only if the next then() handler is elsewhere):返回数据以传递给下一个处理程序(在此示例中您不会使用它,仅当下一个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 */
  });
  • Using the data within the same handler:在同一处理程序中使用数据:
tradeOfferDocRef
  .get()
  .then((doc) => {
    const { senderID, receiverID, sendersItems, receiversItems } = doc.data();
    const itemsArray = sendersItems.concat(receiversItems);

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

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

/* use results from above */

Writing to Firestore写入 Firestore

Your current code consists of the following steps:您当前的代码包含以下步骤:

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`

In the above steps, you can see some problems with your promise chaining.在上述步骤中,您可以看到 promise 链接存在一些问题。 But aside from that, you can also see that you create and edit documents one-by-one instead of all-at-once ("atomically").但除此之外,您还可以看到您是一个接一个地创建和编辑文档,而不是一次性(“原子地”)创建和编辑文档。 If any of these writes were to fail, your database ends up in an unknown state.如果其中任何一个写入失败,您的数据库最终会出现未知的 state。 As an example, you could have created and accepted a trade, but failed to create the notification.例如,您可以创建并接受交易,但未能创建通知。

To atomically write to your database, you need to use a batched write where you bundle a bunch of changes together and then send them off to Firestore.要以原子方式写入数据库,您需要使用批处理写入,将一堆更改捆绑在一起,然后将它们发送到 Firestore。 If any of them were to fail, no data is changed in the database.如果其中任何一个失败,则不会更改数据库中的数据。

Next, you store a user's notifications inside of their user document.接下来,您将用户的通知存储在他们的用户文档中。 For a small number of notifications this is fine, but do you need to download all of those notifications if you wanted to pull just an address or phone number like in the example in the above section?对于少量通知,这很好,但如果您只想提取地址或电话号码,如上一节中的示例,您是否需要下载所有这些通知? I recommend splitting them out into their own document (such as /users/{someUserId}/metadata/notifications ), but ideally their own collection (such as /users/{someUserId}/notifications/{someNotificationID} ).我建议将它们分成自己的文档(例如/users/{someUserId}/metadata/notifications ),但最好是他们自己的集合(例如/users/{someUserId}/notifications/{someNotificationID} )。 By placing them in their own collection, you can query them and use QuerySnapshot#docChanges to synchronize changes and use Cloud Firestore triggers to send push notifications.通过将它们放在自己的集合中,您可以查询它们并使用QuerySnapshot#docChanges来同步更改并使用 Cloud Firestore 触发器发送推送通知。

Refactored Function重构 Function

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

Because the steps here depend on each other, this is a good candidate to use async / await syntax.因为这里的步骤相互依赖,所以这是使用async / await语法的好选择。

To see this in action, closely study this:要看到这一点,请仔细研究:

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,
    },
  };
}

Usage:用法:

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
}

In general, when you are returning a promise where it can't be resolved you must await its result.通常,当您返回无法解决的 promise 时,您必须等待其结果。 Additionally, you must be returning a value from within a promise then chain, at minimal the last .then() needs to be returning a value, this can also be done within a .finally() method.此外,您必须从 promise then链中返回一个值,至少最后一个 .then .then()需要返回一个值,这也可以在.finally()方法中完成。

Using Get from any firebase resource, realtime, firestore, and storage are all Async processes and must be awaited.使用从任何 firebase 资源中获取,实时、firestore 和存储都是异步进程,必须等待。 in your case, you are missing an await for the return:在您的情况下,您错过了等待退货的机会:

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

and you don't appear to be returning anything inside your.then() statements, I would suggest a complete rewrite of what you are trying to so you are returning values as they are needed.并且您似乎没有在 your.then() 语句中返回任何内容,我建议您完全重写您正在尝试的内容,以便您在需要时返回值。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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