在 Knex.js 中的 transaction.commit 之后插入执行

Inserts executing after transaction.commit in Knex.js

我想在完成事务之前在事务中并行执行插入。我使用了 Promise.all() 并且 bluebird 承诺如果失败则取消所有承诺。问题是承诺似乎在实际执行插入之前就结束了。我正在使用 Knex.js。我有 2 个承诺,一个是在用户 table 中插入用户的用户名和电子邮件,另一个是加密用户密码并在登录 table 中插入电子邮件和加密密码。

我找到了执行承诺和插入的顺序。他们以这种方式执行。 (插入用户名和电子邮件的承诺得到解决)->(Knex 调试器说用户名和电子邮件的插入命令是 运行)->(承诺比插入电子邮件和密码得到解决)->(transaction.commit) -> (Knex 调试器说电子邮件和密码的插入命令是 运行,但事务已经结束并抛出错误)。这里的问题显然是在执行电子邮件和密码插入之前执行电子邮件和密码承诺。

const addUser = (username, email, password) => {
    return db.transaction(trx => {
        let addLoginEntry = Promise.resolve(bcrypt.hash(password, 10)
            .then(secret => {
                trx("login").insert({
                    email: email,
                    secret: secret
                }).then(console.log("PASSWORD INSERTED"));
            })
        );

        let addUserEntry = Promise.resolve(
            trx("users").insert({
                username: username,
                email: email
            })
            .then(console.log("USER INFO INSERTED"))
        )

        Promise.all([
            addLoginEntry,
            addUserEntry
        ])
        .then(args => {
            console.log("All promises done");
            trx.commit(args);
        })
        .catch(error => {
            [addLoginEntry, addUserEntry].forEach(promise =>promise.cancel());
            console.log(error);
            trx.rollback();
        });
    });
}

我希望登录 table 和用户 table 都被更新,但是由于事务提交发生在登录更新被添加到事务之前,所以只有用户 table 已更新。以下是我在 运行 在 Knex 中使用 debugging=true 设置程序时得到的错误消息:

USER INFO INSERTED
{ method: 'insert',
  options: {},
  timeout: false,
  cancelOnTimeout: false,
  bindings: [ 'testemail@test.com', 'test' ],
  __knexQueryUid: '2b1d59b1-1246-4237-87f1-d3fbfff7ba80',
  sql: 'insert into "users" ("email", "username") values (?, ?)',
  returning: undefined }
PASSWORD INSERTED
All promises done
{ method: 'insert',
  options: {},
  timeout: false,
  cancelOnTimeout: false,
  bindings:
   [ 'testemail@test.com',
     'b$D.qlOo7aDv4WCzssXGXuQeXQ3lZwWZ1.b1CRIn4DuSD.6ov.jzhBm' ],
  __knexQueryUid: 'e3afdc4a-53bd-4f0d-ad71-7aab0d92d014',
  sql: 'insert into "login" ("email", "secret") values (?, ?)',
  returning: undefined }
Unhandled rejection Error: Transaction query already complete, run with DEBUG=knex:tx for more info
    at completedError (C:\PATH_TO\node_modules\knex\src\transaction.js:338:9)
    at C:\PATH_TO\node_modules\knex\src\transaction.js:304:24
    at Promise.cancellationExecute [as _execute] (C:\PATH_TO\node_modules\bluebird\js\release\debuggability.js:335:9)
    at Promise._resolveFromExecutor (C:\PATH_TO\node_modules\bluebird\js\release\promise.js:488:18)
    at new Promise (C:\PATH_TO\node_modules\bluebird\js\release\promise.js:79:10)
    at Client_PG.trxClient.query (C:\PATH_TO\node_modules\knex\src\transaction.js:300:12)
    at Runner.query (C:\PATH_TO\node_modules\knex\src\runner.js:136:36)
    at C:\PATH_TO\node_modules\knex\src\runner.js:40:23
    at tryCatcher (C:\PATH_TO\node_modules\bluebird\js\release\util.js:16:23)
    at C:\PATH_TO\node_modules\bluebird\js\release\using.js:185:26
    at tryCatcher (C:\PATH_TO\node_modules\bluebird\js\release\util.js:16:23)
    at Promise._settlePromiseFromHandler (C:\PATH_TO\node_modules\bluebird\js\release\promise.js:517:31)
    at Promise._settlePromise (C:\PATH_TO\node_modules\bluebird\js\release\promise.js:574:18)
    at Promise._settlePromise0 (C:\PATH_TO\node_modules\bluebird\js\release\promise.js:619:10)
    at Promise._settlePromises (C:\PATH_TO\node_modules\bluebird\js\release\promise.js:699:18)
    at Promise._fulfill (C:\PATH_TO\node_modules\bluebird\js\release\promise.js:643:18)

您在那里缺少一个 return 语句,并且您的调试打印代码也有错误。我添加了评论来解释那里发生了什么:

return db.transaction(trx => {
    let addLoginEntry = Promise.resolve(bcrypt.hash(password, 10)
        .then(secret => {
            // ---- MISSING RETURN HERE and PASSWORD INSERTED
            // actually runs before query is even executed.
            // should be .then(() => console.log("PASSWORD INSERTED"))
            // to make that debug print line to be evaluated as part of
            // promise chain
            trx("login").insert({
                email: email,
                secret: secret
            }).then(console.log("PASSWORD INSERTED"));
        })
    );

    // also here USER INFO INSERTED is logged before
    // query is even executed during evaluating query builder
    // method parameters
    let addUserEntry = Promise.resolve(
        trx("users").insert({
            username: username,
            email: email
        })
        .then(console.log("USER INFO INSERTED"))
    )

    // at this point of code USER INFO INSERTED is already printed
    // user add query is ran concurrently with bcrypt call and then 
    // this is resolved and execution continues ....
    Promise.all([
        addLoginEntry,
        addUserEntry
    ])
    .then(args => {
        // .... continues here and concurrently also login insert query is 
        // created and PASSWORD INSERTED log is printed out
        console.log("All promises done");

        // for some reason .commit() gets executed before login insert query is
        // actually triggered. It could have also worked correctly with
        // some luck.
        trx.commit(args);
    })
    .catch(error => {
        [addLoginEntry, addUserEntry].forEach(promise =>promise.cancel());
        console.log(error);
        trx.rollback();
    });
});

所以是的,基本上只缺少一个 return 语句。