【问题标题】:Knex Record Insertion Excecuting Out of OrderKnex 记录插入执行乱序
【发布时间】:2018-05-10 07:50:33
【问题描述】:

在我的测试中尝试使用 Knex 添加记录时,我看到了一些奇怪的异步行为。

POST /api/v1/chats 路由被命中时,chat 被创建并添加到chat 表中。此外,在请求正文中传递给路由处理程序的user id(其中有两个)和新创建的chat 的id 用于将两个关联实体添加到user_chat 表中:{ user_id: 2, chat_id: 3 }{ user_id: 4, chat_id: 3 }。记录id为4和2的users在id为3的chat中。

但是,addUserChat 方法/查询没有按我期望的方式执行。从控制台中可以看出,即从该函数调用开始的控制台日志发生两次,然后从函数结束的第一次控制台日志发生一次。

显然,在下一个调用控制之前,有一些异步调用没有得到解决。但我很难追踪发生这种情况的地方。在我看来,这应该不是问题,因为我正在 awaiting 在第 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();
                });
        });

项目的所有代码:https://github.com/caseysiebel/lang-exchange

【问题讨论】:

    标签: node.js postgresql asynchronous async-await knex.js


    【解决方案1】:

    如果我的问题是正确的,那么您的问题是您不想并行运行多个 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('*')
    

    【讨论】:

    • 这是有道理的。我使用await Promise.all 方法的动机是,如果有很长的user id 列表,那么我不想在调度下一个异步调用之前等待每个ID 完成,尽管性能可能不是这是一个巨大的问题。所以我想正在发生的事情是user_id 被第二次调用addUserChat 设置为4,然后当第一次调用完成时,它的执行user_id 在这种情况下也是4。为什么会这样?我该如何解决?在我看来,user_id 在第一个上下文中应该是 2
    • @Casey 现在我明白了你的问题,更新了答案
    • 感谢您的回答!我使用了您提供的信息并将我的查询从 await userChats.... 更改为 await db('user_chat').... 并解决了问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-05-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-29
    相关资源
    最近更新 更多