【问题标题】:Getting error: `Every document read in a transaction must also be written` in Firebase出现错误:`在事务中读取的每个文档也必须在 Firebase 中写入`
【发布时间】:2018-11-07 05:26:48
【问题描述】:

伙计们!

我正在开发一个类似于https://airtasker.com 的应用程序,用户将任务外包。 任务者将对任务出价,并等待用户批准他们的出价。

这些是涉及的集合:

  • 任务
  • 交易
  • 出价

基本上,这个函数应该:

检查是否存在具有给定 taskId 的事务。 如果用户开始批准出价,则添加交易。我允许多个任务者完成任务。

如果交易不存在,它应该 添加一个新的,如果达到所需的人力,则将状态标记为正在进行(否则为待处理),并更新投标集合以将投标标记为已接受。

如果交易存在,它应该 检查事务集合中的当前批准列表是否等于人力

如果还没有达到配额人力,推送一个新的任务者并访问投标集合以标记投标接受。

如果在最后一个条件之后,批准的列表已经达到配额人力,则标记任务关闭,并将交易状态更改为进行中

但我不断收到此错误:

Uncaught (in promise) 错误:事务中读取的每个文档也必须写入。 在 Transaction.commit (transaction.js:128) 在 eval (sync_engine.js:244)

这是我的代码:

const acceptOffer = async (taskerId, taskId, bidId, offer) => {
  let bulk
  try {
    const taskRef = db.collection('tasks').doc(taskId)
    const transRef = db.collection('transactions').doc(taskId)
    const bidRef = db.collection('bids').doc(bidId)
    const fees = solveFees(offer)

    bulk = await db
      .runTransaction(async t => {
        const transdoc = await t.get(transRef)
        const taskdoc = await t.get(taskRef)
        const manpower = await taskdoc.get('manpower')

        let status = 'pending'
        if (manpower === 1) {
          status = 'ongoing'
        }

        if (!transdoc.exists) {
          t.set(transRef, {
            taskId,
            status, // pending, ongoing, completed
            approved: [
              { taskerId, ...fees }
            ]
          })

          t.update(bidRef, {
            accepted: true
          })
        } else {
          const approved = await transdoc.get('approved')
          if (manpower < approved.length) {
            approved.push({ taskerId, ...fees })
            t.update(transRef, { approved })
            t.update(bidRef, { accepted: true })

            if (manpower === approved.length) {
              t.update(taskRef, { open: false })
              t.update(transRef, { status: 'ongoing' })
            }
          }
        }
      })
  } catch (e) {
    bulk = e
    console.log('nag error', e)
    throw e
  }

  if (bulk.success) {
    swal('Offer accepted!', '', 'success')
  } else {
    swal('Oh, no!',
      'This task might already be approved',
      'error'
    )
  }
}

我一直被困在这里,因为我不明白交易失败的地方。很感谢任何形式的帮助。

谢谢!

【问题讨论】:

    标签: javascript firebase ecmascript-6 async-await google-cloud-firestore


    【解决方案1】:

    对于那些有同样问题的人,这是我的(hackish)解决方案:

    对于每个条件,

    添加一个文档写入(可以是set() update()delete()),对应于我的代码中的每个文档读取:get()s 的使用。

    并返回一个承诺

    这是更新后的代码:

    // a transaction is added if the user starts to approve offers
    // this function allows multiple taskers
    const acceptOffer = async (taskerId, taskId, bidId, offer) => {
      let bulk
      try {
        const taskRef = db.collection('tasks').doc(taskId)
        const transRef = db.collection('transactions').doc(taskId)
        const bidRef = db.collection('bids').doc(bidId)
    
        const fees = solveFees(offer)
    
        bulk = await db
          .runTransaction(async t => {
            const transdoc = await t.get(transRef)
            const taskdoc = await t.get(taskRef)
            const manpower = await taskdoc.get('manpower')
    
            // check if a transaction exists with the given taskId
            // if it doesn't, then the task doesn't have
            // any approved bidders yet
            if (!transdoc.exists) {
              // check if there is only one manpower required for the task
              // mark the status of the transaction 'ongoing' if so
              const status = manpower === 1
                ? 'ongoing' : 'pending'
    
              // add a transaction with the approved tasker
              t.set(transRef, {
                taskId,
                status, // pending, ongoing, completed
                approved: [
                  { taskerId, ...fees }
                ]
              })
    
              // mark the bid 'accepted'
              t.update(bidRef, {
                accepted: true
              })
    
              // hackish (to prevent firestore transaction errors)
              t.update(taskRef, {})
    
              return Promise.resolve(true)
            } else { // if a transaction exists with the given taskId
              const approved = await transdoc.get('approved')
    
              // check if the current approved list from
              // the transactions collection hasn't
              // reached the manpower quota yet
              if (approved.length < manpower) {
                // push new approved bid of the tasker
                approved.push({ taskerId, ...fees })
                t.update(transRef, { approved })
    
                t.update(bidRef, { accepted: true }) // mark the bid 'accepted'
                t.update(taskRef, {}) // hackish
    
                // if, after pushing a new transaction,
                // the approved list reached the manpower quota
                if (approved.length === manpower) {
                  t.update(taskRef, { open: false }) // mark the task 'close'
                  t.update(transRef, { status: 'ongoing' }) // mark the transaction 'ongoing'
                  t.update(bidRef, {}) // hackish
                }
                return Promise.resolve(true)
              }
              return Promise.reject(new Error('Task closed!'))
            }
          })
      } catch (e) {
        swal('Oh, no!',
          'This task might already be closed',
          'error'
        )
        throw e
      }
    
      if (bulk) {
        swal('Offer accepted!', '', 'success')
      }
    }
    

    【讨论】:

      【解决方案2】:

      我遇到了同样的问题。只要谷歌将无法发送比客户端不允许写入数据(安全规则)更好的错误的验证错误。我更喜欢在客户网站上处理它。因此,例如,我使用事务来验证在我写入数据时引用的文档是否仍然可用。 (例如我写了一个order 文档,它引用了customer 并希望确保客户仍然存在。)所以我必须阅读它,但实际上没有必要写它。

      我想出了一些接近 nrions 的解决方案,但我试图有一个更通用的方法,所以我为 runTransaction 编写了一个包装器。当然,这不是最干净的方法,但也许对其他人有用。

      // Transaction neads to write all docs read be transaction.get().
      // To work around this we we call an update with {} for each document requested by transaction.get() before writing any data
      export function runTransaction(updateFunction) {
        return db.runTransaction(transaction => {
          const docRefsRequested = [];
          let didSetRequestedDocs = false;
      
          function setEachRequestedDoc() {
            if (didSetRequestedDocs) {
              return;
            }
            didSetRequestedDocs = true;
            docRefsRequested.forEach(({ exists, ref }) => {
              if (exists) {
                transaction.update(ref, {});
              } else {
                transaction.delete(ref);
              }
            });
          }
      
          const transactionWrapper = {
            get: function(documentRef) {
              return transaction.get(ref).then(snapshot => {
                const { exists } = snapshot;
                docRefsRequested.push({ ref, exists });
                return Promise.resolve(snapshot);
              });
            },
            set: function(documentRef, data) {
              setEachRequestedDoc();
              return transaction.set(documentRef, data);
            },
            update: function(documentRef, data) {
              setEachRequestedDoc();
              return transaction.update(documentRef, data);
            },
            delete: function(documentRef) {
              setEachRequestedDoc();
              return transaction.delete(documentRef);
            },
          };
      
          return updateFunction(transactionWrapper).then(resolveValue => {
            setEachRequestedDoc();
            return Promise.resolve(resolveValue);
          });
        });
      }
      

      【讨论】:

        猜你喜欢
        • 2020-09-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多