Knexjs如何处理多次更新

Knexjs how to handle multiple updates

我不太确定如何处理 knex 中的多个更新/插入,return 无论最后是否成功。

我通过 req.body 循环传递数组并根据数组内的信息触发操作。

示例:

const data = [...req.body]
for(let i = 0; i < data.length; i++) {
        data[i].totals.length
        for(let y = 0; y < data[i].totals.length; y++) {
            if(data[i].totals[y].info === "Holiday") {
                calcHoliday(data[i].totals[y].total, data[i].id)
            } else if(data[i].totals[y].info === "ZA") {
                calcZA(data[i].totals[y].total, data[i].id)
            }
        }
        calcOvertime(data[i].totalSum, data[i].id)

        if(i === data.length -1) {
            res.json("Success")
        }
    }

我传入的数组如下所示:

[
    {
    "id": 1,
    "totals": [
        {
            "info": "Holiday",
            "total": 4
        }
    ]
    },
    {
    "id": 1,
    "totals": [
        {
            "info": "Holiday",
            "total": 4
        }
    ]
    }
]

在for循环中调用的函数示例:

const calcHoliday = (hours, userid) => {
        knex.transaction(trx => {
            trx.insert({
                created_at: convertedTime,
                info: "Booking Holiday - Hours: " + hours,
                statuscode: 200
            }).into("logs")
                .then(() => {
                    return trx("hours")
                        .decrement("holiday_hours", hours)
                }).then(trx.commit)
                .catch(trx.rollback)
        }).then(() => console.log("WORKED"))
            .catch(err => console.log(err))
}

这工作得很好,但我不知道如何从每个 table 更新中收集结果,以便在一切正常或出现错误时做出响应。如果我打电话给在一次 calcHoliday 调用后 .then(resp => res.json(resp) 我只收到第一次操作的响应。

简而言之,如果一切都成功或某处出现错误,我需要一种方法来res.json。

提前致谢!

TLDR;

将您的插入调用转换为承诺数组,然后使用 awaitPromise.all() / Promise.allSettled() 结构可能会解决此问题,但需要做出一些 UX 决定回滚什么以及如何回滚 return 错误。

错误处理选项:

  1. 任何错误 --> 所有 所有循环迭代中的插入都应回滚
  • 你想要部分成功吗?现在编写代码的方式,回滚仅适用于一个函数调用中的项目。如果 hour-decrement 调用之一失败,它将回滚一个 log 插入,但不会回滚循环中先前数据的任何成功插入。如果您希望整个数据集回滚,则需要通过每个函数调用传递 txn 在一个函数调用中批量插入所有行,出于性能原因,这可能会很好,具体取决于用例。
  1. 部分成功 --> 提交成功,回滚失败的单循环迭代,发送错误和成功的详细列表
  • 您想使用 Promise.allSettled(),它将循环中所有承诺的成功和错误聚合为一个数组。
  1. 部分成功 --> 提交成功,回滚失败的单循环迭代,仅发送一个错误
  • 意见:这可能是误导性的用户体验,除非错误是“一些插入不成功”并且端点是幂等的
  • 这看起来与您所描述的最接近。如果是这种情况,你会想要使用 Promise.all(),一旦数组中的一个承诺出错,它就会抛出一个错误。

示例实现:

由于原始代码不完整,所以这是选项 2/3 可能是什么样子的松散、不完整的示例。这可以很容易地转换为选项 1。

首先,它可能有助于修改所有具有异步调用的函数,使其可以作为承诺实现。 Async/await 有助于避免难以推理的 .then() 树。

const calcHoliday = async (hours, userid) => {
  try {
    const result = await knex.transaction(async(trx) => {
      await trx.insert({
        created_at: convertedTime,
        info: "Booking Holiday - Hours: " + hours,
        statuscode: 200
      }).into("logs")

      return trx("hours").decrement("holiday_hours", hours)
    }

    return result
  } catch(err) {
    console.log("It didn't work.")
    throw new Error(`Error: Failure to insert for user ${userid}:`, err)
  }
}

这里有一些实用程序可以转换数据,并在 Promise 中获取适当的未履行承诺以提供给地图。all/allSettled。

/*
 Here's an example of how you might transform the original data with maps in order to avoid nested for-loops:
  [
    { id: 1, info: 'Holiday', total: 4 },
    { id: 1, info: 'Holiday', total: 4 }
  ]
*/
const flatData = data.map(item => {
  return item.totals.map(total => ({
    id: item.id,
    ...total
  }))
}).flat()


// Returns the appropriate promise based on data
const getTotalsPromises = (row) => {
  const {info, id, total} = row
  if(info === "Holiday") {
      return calcHoliday(total, id)
  } else if(info === "ZA") {
      return calcZA(total, id)
  }
}

const getcalcOvertimePromises = (rowInData) = {
  // work left to reader
  return calcOvertime(rowInData.correctData, rowInData.otherData)
}

如果您想要选项 2:

// Replaces the loop

// Fulfills *all* the promises, creating an array of errors and successes
const responses = await Promise.allSettled([
  ...flatData.map(getTotalsPromises),
  ...data.map(getCalcOvertimePromises)
])

// insert loop here to do something with errors if you want

res.send(responses)

选项3 创建一个数组,包含您想要 运行、运行 的所有承诺,并最多处理一个错误。

// Replaces the loop

// Runs the promises and waits for them all to finish or the first error.

try {
  const responses = await Promise.all([
    ...flatData.map(getTotalsPromises),
    ...data.map(getCalcOvertimePromises)
  ])
  res.send(responses)
} catch(err){
  // Reached if one of the rows errors
  res.send(err)
}

文档: Promise.allSettled Promise.all