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;
将您的插入调用转换为承诺数组,然后使用 await
和 Promise.all()
/ Promise.allSettled()
结构可能会解决此问题,但需要做出一些 UX 决定回滚什么以及如何回滚 return 错误。
错误处理选项:
- 任何错误 --> 所有 所有循环迭代中的插入都应回滚
- 你想要部分成功吗?现在编写代码的方式,回滚仅适用于一个函数调用中的项目。如果
hour-decrement
调用之一失败,它将回滚一个 log
插入,但不会回滚循环中先前数据的任何成功插入。如果您希望整个数据集回滚,则需要通过每个函数调用传递 txn
或 在一个函数调用中批量插入所有行,出于性能原因,这可能会很好,具体取决于用例。
- 部分成功 --> 提交成功,回滚失败的单循环迭代,发送错误和成功的详细列表
- 您想使用
Promise.allSettled()
,它将循环中所有承诺的成功和错误聚合为一个数组。
- 部分成功 --> 提交成功,回滚失败的单循环迭代,仅发送一个错误
- 意见:这可能是误导性的用户体验,除非错误是“一些插入不成功”并且端点是幂等的
- 这看起来与您所描述的最接近。如果是这种情况,你会想要使用
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)
}
我不太确定如何处理 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;
将您的插入调用转换为承诺数组,然后使用 await
和 Promise.all()
/ Promise.allSettled()
结构可能会解决此问题,但需要做出一些 UX 决定回滚什么以及如何回滚 return 错误。
错误处理选项:
- 任何错误 --> 所有 所有循环迭代中的插入都应回滚
- 你想要部分成功吗?现在编写代码的方式,回滚仅适用于一个函数调用中的项目。如果
hour-decrement
调用之一失败,它将回滚一个log
插入,但不会回滚循环中先前数据的任何成功插入。如果您希望整个数据集回滚,则需要通过每个函数调用传递txn
或 在一个函数调用中批量插入所有行,出于性能原因,这可能会很好,具体取决于用例。
- 部分成功 --> 提交成功,回滚失败的单循环迭代,发送错误和成功的详细列表
- 您想使用
Promise.allSettled()
,它将循环中所有承诺的成功和错误聚合为一个数组。
- 部分成功 --> 提交成功,回滚失败的单循环迭代,仅发送一个错误
- 意见:这可能是误导性的用户体验,除非错误是“一些插入不成功”并且端点是幂等的
- 这看起来与您所描述的最接近。如果是这种情况,你会想要使用
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)
}