Knex 记录插入乱序执行
Knex Record Insertion Excecuting Out of Order
我在测试中尝试使用 Knex 添加记录时看到一些奇怪的异步行为。
当 POST
/api/v1/chats
路由被命中时,一个 chat
被创建并添加到 chat
table。此外,在请求体中传递给路由处理程序的 user
id(有 2 个)和新创建的 chat
的 id 用于将两个关联实体添加到 user_chat
table:{ user_id: 2, chat_id: 3 }
和 { user_id: 4, chat_id: 3 }
。记录id为4和2的user
在id为3的chat
中
但是,addUserChat
method/query 没有按照我期望的方式执行。从控制台中可以看出,即从该函数调用开始的控制台日志发生两次,然后第一次从函数结束的控制台日志发生一次。
显然有一些异步调用在下一个调用控制之前没有得到解决。但是我很难找到发生这种情况的地方。在我看来,这应该不是问题,因为我正在 await
在第 13 行创建 user_chat
记录,但显然我错了,因为这种行为已经过时了。
我很好奇如何解决这个问题,以便例程按顺序运行并且我的 user_chat
都按预期插入。
添加用户聊天:
https://github.com/caseysiebel/lang-exchange/blob/master/src/server/db/queries/user_chat.js#L7
addUserChat: ( async (user_id, chat_id) => {
console.log()
console.log('====================================================================================================')
console.log('in query')
console.log('user_id', user_id)
console.log('chat_id', chat_id)
console.log()
console.log('before await userChats')
const user_chat = await userChats
.insert({ user_id, chat_id })
.returning('*')
console.log('after await userChats')
console.log('user_chat', user_chat);
console.log()
const data = await db('user_chat').select('*')
console.log('data', data)
console.log('****************************************************************************************************')
console.log()
return user_chat;
}),
控制台输出(https://gist.github.com/caseysiebel/262997efdd6467c72304ee783dadd9af#file-console-L5):
====================================================================================================
in query
user_id 2
chat_id 3
before await userChats
====================================================================================================
in query
user_id 4
chat_id 3
before await userChats
after await userChats
user_chat [ anonymous { id: 5, user_id: '4', chat_id: '3' } ]
after await userChats
user_chat [ anonymous { id: 6, user_id: '4', chat_id: '3' } ]
data [ anonymous { id: 1, user_id: '1', chat_id: '1' },
anonymous { id: 2, user_id: '2', chat_id: '2' },
anonymous { id: 3, user_id: '3', chat_id: '2' },
anonymous { id: 4, user_id: '4', chat_id: '1' },
anonymous { id: 5, user_id: '4', chat_id: '3' },
anonymous { id: 6, user_id: '4', chat_id: '3' } ]
****************************************************************************************************
data [ anonymous { id: 1, user_id: '1', chat_id: '1' },
anonymous { id: 2, user_id: '2', chat_id: '2' },
anonymous { id: 3, user_id: '3', chat_id: '2' },
anonymous { id: 4, user_id: '4', chat_id: '1' },
anonymous { id: 5, user_id: '4', chat_id: '3' },
anonymous { id: 6, user_id: '4', chat_id: '3' } ]
****************************************************************************************************
其他相关代码是 POST
/api/v1/chat
路由处理程序:
router.post('/api/v1/chat', async (ctx) => {
try {
const { created_at , user_ids } = ctx.request.body;
const chat_list = await queries.addChat({ created_at });
const chat = chat_list[0];
if (chat) {
ctx.status = 201;
ctx.body = {
status: 'success',
data: chat
};
try {
console.log('user_ids', user_ids)
await Promise.all(user_ids.map((user_id) => {
return user_chat_queries.addUserChat(user_id, chat.id)
}));
}
catch (err) {
ctx.status = 400;
ctx.body = {
status: 'error',
message: err.chat || 'Sorry, an error has occured.'
};
}
}
else {
ctx.status = 400;
ctx.body = {
status: 'error',
message: 'Something went wrong.'
};
}
}
catch (err) {
ctx.status = 400;
ctx.body = {
status: 'error',
message: err.chat || 'Sorry, an error has occured.'
};
}
})
发起来电的聊天路径测试
it('should add 2 user_chats', (done) => {
console.log('00000000000000000000000000000000000000000000000000')
chai.request(server)
.post('/api/v1/chats')
.send({
created_at: Date.now(),
user_ids: [ 2, 4 ]
})
.end((err, res) => {
should.not.exist(err);
res.status.should.equal(201);
res.type.should.equal('application/json');
res.body.status.should.eql('success');
const chat = res.body.data;
chat.should.include.keys('id', 'created_at');
let num_user_chats = 0;
console.log('&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&')
console.log('chat', chat)
console.log('chat.id', chat.id)
console.log('&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&')
knex('user_chat')
.select('*')
.then((data) => console.log('data', data))
knex('user_chat')
.where('user_id', 2)
.select('*')
.then((data) => console.log('data', data))
console.log('user_chat', user_chat);
done();
});
});
如果我答对了,你的问题是你不想 运行 多个 addUserChat
并行调用。
让它们运行平行的地方在这里:
await Promise.all(user_ids.map((user_id) => {
return user_chat_queries.addUserChat(user_id, chat.id)
}));
按顺序 运行 他们可以这样做:
for (let user_id of user_ids) {
await user_chat_queries.addUserChat(user_id, chat.id)
}
使它们 运行 顺序排列的另一种更好的方法是使用事务。
编辑在@Casey 的评论后理解了真正的问题:
我想您的 userChats
是一些预定义的查询生成器。所以你一次又一次地使用同一个构建器来处理所有单独的插入。
所以基本上在第一个用户 ID 上你的查询是这样的:
const user_chat = await userChats
.insert({ user_id, chat_id }) // user_id = 2
.returning('*')
第二轮是:
const user_chat = await userChats
.returning('*')
.insert({ user_id, chat_id }) // user_id = 2
.insert({ user_id, chat_id }) // user_id = 4
.returning('*');
现在第二个查询实际上是在发出第一个查询之前构建的,因此两个查询实际上是相同的:
const user_chat = await userChats
.returning('*')
.insert({ user_id, chat_id }) // user_id = 2
.insert({ user_id, chat_id }) // user_id = 4
.returning('*');
您可以通过记住为每个查询克隆构建器来解决此问题:
const user_chat = await userChats.clone()
.insert({ user_id, chat_id })
.returning('*')
我在测试中尝试使用 Knex 添加记录时看到一些奇怪的异步行为。
当 POST
/api/v1/chats
路由被命中时,一个 chat
被创建并添加到 chat
table。此外,在请求体中传递给路由处理程序的 user
id(有 2 个)和新创建的 chat
的 id 用于将两个关联实体添加到 user_chat
table:{ user_id: 2, chat_id: 3 }
和 { user_id: 4, chat_id: 3 }
。记录id为4和2的user
在id为3的chat
中
但是,addUserChat
method/query 没有按照我期望的方式执行。从控制台中可以看出,即从该函数调用开始的控制台日志发生两次,然后第一次从函数结束的控制台日志发生一次。
显然有一些异步调用在下一个调用控制之前没有得到解决。但是我很难找到发生这种情况的地方。在我看来,这应该不是问题,因为我正在 await
在第 13 行创建 user_chat
记录,但显然我错了,因为这种行为已经过时了。
我很好奇如何解决这个问题,以便例程按顺序运行并且我的 user_chat
都按预期插入。
添加用户聊天: https://github.com/caseysiebel/lang-exchange/blob/master/src/server/db/queries/user_chat.js#L7
addUserChat: ( async (user_id, chat_id) => {
console.log()
console.log('====================================================================================================')
console.log('in query')
console.log('user_id', user_id)
console.log('chat_id', chat_id)
console.log()
console.log('before await userChats')
const user_chat = await userChats
.insert({ user_id, chat_id })
.returning('*')
console.log('after await userChats')
console.log('user_chat', user_chat);
console.log()
const data = await db('user_chat').select('*')
console.log('data', data)
console.log('****************************************************************************************************')
console.log()
return user_chat;
}),
控制台输出(https://gist.github.com/caseysiebel/262997efdd6467c72304ee783dadd9af#file-console-L5):
====================================================================================================
in query
user_id 2
chat_id 3
before await userChats
====================================================================================================
in query
user_id 4
chat_id 3
before await userChats
after await userChats
user_chat [ anonymous { id: 5, user_id: '4', chat_id: '3' } ]
after await userChats
user_chat [ anonymous { id: 6, user_id: '4', chat_id: '3' } ]
data [ anonymous { id: 1, user_id: '1', chat_id: '1' },
anonymous { id: 2, user_id: '2', chat_id: '2' },
anonymous { id: 3, user_id: '3', chat_id: '2' },
anonymous { id: 4, user_id: '4', chat_id: '1' },
anonymous { id: 5, user_id: '4', chat_id: '3' },
anonymous { id: 6, user_id: '4', chat_id: '3' } ]
****************************************************************************************************
data [ anonymous { id: 1, user_id: '1', chat_id: '1' },
anonymous { id: 2, user_id: '2', chat_id: '2' },
anonymous { id: 3, user_id: '3', chat_id: '2' },
anonymous { id: 4, user_id: '4', chat_id: '1' },
anonymous { id: 5, user_id: '4', chat_id: '3' },
anonymous { id: 6, user_id: '4', chat_id: '3' } ]
****************************************************************************************************
其他相关代码是 POST
/api/v1/chat
路由处理程序:
router.post('/api/v1/chat', async (ctx) => {
try {
const { created_at , user_ids } = ctx.request.body;
const chat_list = await queries.addChat({ created_at });
const chat = chat_list[0];
if (chat) {
ctx.status = 201;
ctx.body = {
status: 'success',
data: chat
};
try {
console.log('user_ids', user_ids)
await Promise.all(user_ids.map((user_id) => {
return user_chat_queries.addUserChat(user_id, chat.id)
}));
}
catch (err) {
ctx.status = 400;
ctx.body = {
status: 'error',
message: err.chat || 'Sorry, an error has occured.'
};
}
}
else {
ctx.status = 400;
ctx.body = {
status: 'error',
message: 'Something went wrong.'
};
}
}
catch (err) {
ctx.status = 400;
ctx.body = {
status: 'error',
message: err.chat || 'Sorry, an error has occured.'
};
}
})
发起来电的聊天路径测试
it('should add 2 user_chats', (done) => {
console.log('00000000000000000000000000000000000000000000000000')
chai.request(server)
.post('/api/v1/chats')
.send({
created_at: Date.now(),
user_ids: [ 2, 4 ]
})
.end((err, res) => {
should.not.exist(err);
res.status.should.equal(201);
res.type.should.equal('application/json');
res.body.status.should.eql('success');
const chat = res.body.data;
chat.should.include.keys('id', 'created_at');
let num_user_chats = 0;
console.log('&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&')
console.log('chat', chat)
console.log('chat.id', chat.id)
console.log('&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&')
knex('user_chat')
.select('*')
.then((data) => console.log('data', data))
knex('user_chat')
.where('user_id', 2)
.select('*')
.then((data) => console.log('data', data))
console.log('user_chat', user_chat);
done();
});
});
如果我答对了,你的问题是你不想 运行 多个 addUserChat
并行调用。
让它们运行平行的地方在这里:
await Promise.all(user_ids.map((user_id) => {
return user_chat_queries.addUserChat(user_id, chat.id)
}));
按顺序 运行 他们可以这样做:
for (let user_id of user_ids) {
await user_chat_queries.addUserChat(user_id, chat.id)
}
使它们 运行 顺序排列的另一种更好的方法是使用事务。
编辑在@Casey 的评论后理解了真正的问题:
我想您的 userChats
是一些预定义的查询生成器。所以你一次又一次地使用同一个构建器来处理所有单独的插入。
所以基本上在第一个用户 ID 上你的查询是这样的:
const user_chat = await userChats
.insert({ user_id, chat_id }) // user_id = 2
.returning('*')
第二轮是:
const user_chat = await userChats
.returning('*')
.insert({ user_id, chat_id }) // user_id = 2
.insert({ user_id, chat_id }) // user_id = 4
.returning('*');
现在第二个查询实际上是在发出第一个查询之前构建的,因此两个查询实际上是相同的:
const user_chat = await userChats
.returning('*')
.insert({ user_id, chat_id }) // user_id = 2
.insert({ user_id, chat_id }) // user_id = 4
.returning('*');
您可以通过记住为每个查询克隆构建器来解决此问题:
const user_chat = await userChats.clone()
.insert({ user_id, chat_id })
.returning('*')