获取错误:“在事务中读取的每个文档也必须在Firebase中写入”

时间:2018-04-11 02:13:29

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

人!

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

这些是涉及的集合:

  • 任务
  • 交易
  • 出价
基本上,这个函数应该是:

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

如果交易不存在,则应该 添加新的状态,如果达到所需的人力(等待处理),则标记状态正在进行,并更新出价集合以标记接受的出价。

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

如果尚未达到配额人力,请推送新任务并访问出价集合以标记已接受的出价。

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

但我一直收到此错误:

  

未捕获(在承诺中)错误:还必须编写在事务中读取的每个文档。       在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'
    )
  }
}

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

谢谢你!

2 个答案:

答案 0 :(得分:1)

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

适用于所有条件,

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

并返回Promise

这是更新后的代码:

// 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')
  }
}

答案 1 :(得分:0)

我遇到了同样的问题。只要Google不能发送验证错误,而错误信息的错误要大于不允许客户端写入数据的错误(安全规则)。我更喜欢在客户端站点上处理它。因此,例如,我使用事务来验证写入数据时引用的文档仍然可用。 (例如,我已经编写了一个引用order的{​​{1}}文档,并希望确保该客户仍然存在。)因此,我必须阅读它,但实际上不需要编写它。 / p>

我想出了一些与nrions解决方案相似的方法,但是尝试使用一种更通用的方法,因此我为runTransaction写了一个包装器。当然,这不是最干净的方法,但也许对其他人有用。

customer