0

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

我找到了执行承诺和插入的顺序。他们以这种方式执行。(插入用户名和电子邮件的承诺得到解决)->(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();
        });
    });
}

我希望登录表和用户表都被更新,但由于事务提交发生在登录更新被添加到事务之前,因此只更新了用户表。以下是我在 Knex 中使用 debug=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',
     '$2b$10$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)
4

1 回答 1

2

您在那里缺少一条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缺少一个声明。

于 2019-07-16T05:02:09.457 回答