我正在为数据库使用knex.js,并且我有一个查询,该查询取决于上一个查询。
示例:
用户表
|用户名(pk)| first_name | last_name |
登录表
|用户名(pk / fk)|哈希|
过程是:
插入用户>插入以登录
登录取决于用户,因此如果尚未完成向用户的插入操作,它将返回错误。
这是我的代码:
const handleSignup = (req, res, db, logger, bcrypt) => {
const {
username,
password,
firstName,
lastName,
} = req.body;
const hash = bcrypt.hashSync(password);
if (username || !firstName || !lastName ) {
res.json({
haveEmpty: true
});
return;
} else {
db.transaction((trx) => {
db.select('*').from('user').where('username', '=', username)
.then(data => {
if (!data[0]) {
db('user')
.returning('*')
.insert({
username: username,
first_name: firstName,
last_name: lastName,
})
.then(user => {
db('login')
.returning('*')
.insert({
username: username,
hash: hash
})
.then(login => {
if (login[0]) {
res.json({
isSuccess: true
});
return;
} else {
res.json({
isSuccess: false
});
return;
}
})
.then(trx.commit)
.catch(err => {
logger.error(err);
trx.rollback;
res.render('pages/error-500');
});
})
.then(trx.commit)
.catch(err => {
logger.error(err);
trx.rollback;
res.render('pages/error-500');
});
} else {
res.json('User already Exist!');
return;
}
})
.then(trx.commit)
.catch(err => {
logger.error(err);
trx.rollback;
res.render('pages/error-500');
});
})
.catch(err => logger.error(err));
}
}
我不知道我是否正在使用交易权。但这就是我要提出的。以前,当我将查询分为两个Promise时,我收到一个错误,因为似乎第一个插入(用户)未完成。
此代码有效,但我知道有一种更正确的编码方法。
答案 0 :(得分:2)
在then回调中返回Promise将会依次执行promise:
const handleSignup = (req, res, db, logger, bcrypt) => {
const {
username,
password,
firstName,
lastName,
} = req.body;
const hash = bcrypt.hashSync(password);
if (username || !firstName || !lastName) {
res.json({
haveEmpty: true
});
return;
}
db.transaction((trx) => {
db.select('*').from('user').where('username', '=', username)
.then(data => {
if (data[0]) {
res.json('User already Exist!');
return;
}
return db('user')
.returning('*')
.insert({
username: username,
first_name: firstName,
last_name: lastName,
});
})
.then(user => {
return db('login')
.returning('*')
.insert({
username: username,
hash: hash
});
})
.then(login => {
if (!login[0]) {
res.json({
isSuccess: false
});
return;
}
res.json({
isSuccess: true
});
})
.then(trx.commit)
.then(trx.commit)
.then(trx.commit)
.catch(err => {
logger.error(err);
trx.rollback;
res.render('pages/error-500');
});
})
.catch(err => logger.error(err));
}
我不确定您的代码100%是因为您只会回退最后一个查询,而不是全部回退。请注意这一点。
答案 1 :(得分:2)
根据我的经验,一旦您停止尝试将所有功能都塞进相同的功能中,承诺就会变得更加自然! (但是我们每个人可能一次或两次都写过与您的示例相似的内容,请放心。)
更小的代码块也倾向于更易于测试和调试。例如,如果您知道对请求正文中的变量的检查是正确的,则问题可能出在堆栈的下方。
这是一个使用小型中间件堆栈的示例。这样可以将操作分成几小块,同时仍保证一件事会先于另一件事发生。
const bcrypt = require("bcrypt");
const express = require("express");
const knex = require("knex");
const config = require("./knexfile").development;
const app = express();
app.use(express.json());
const db = knex(config);
const detailValidator = (req, res, next) => {
// You can do more robust validation here, of course
if (!req.body.firstName || !req.body.lastName) {
return next(new Error("Missing user details."));
}
next();
};
const userUniqueValidator = (req, res, next) => {
db("users")
.where("username", req.body.username)
.then(users => {
if (users.length !== 0) {
return next(new Error("User exists."));
}
next();
});
};
const userCreator = (req, res, next) => {
const { username, password, firstName, lastName } = req.body;
const hash = bcrypt.hashSync(password, 10);
db.transaction(trx =>
trx("users")
.insert({
username,
first_name: firstName,
last_name: lastName
})
.then(([userId]) => trx("auth").insert({ user_id: userId, hash }))
.then(() => res.json({ success: true }))
).catch(err => next(err));
};
app.post("/", detailValidator, userUniqueValidator, userCreator);
app.use((err, req, res, next) => res.json({ error: err.message }));
app.listen(4000, () => console.log("yup"));
关于Knex中的事务:如果使用上述语法,则实际上根本不需要调用commit
。但是,您确实需要将trx
参数用作查询生成器。该文档还提出了另一种选择,即transacting
语法:请参见docs。
最后,我不建议您使用用户名作为主键。经常需要更改它们,并且始终存在意外泄露URL或日志中的风险。我建议包括一个唯一的约束。也许是这样的事情?
exports.up = knex =>
knex.schema.createTable("users", t => {
t.increments("id");
t.string("username").unique();
t.string("first_name");
t.string("last_name");
});
exports.up = knex =>
knex.schema.createTable("auth", t => {
t.increments("id");
t.integer("user_id").references("users.id");
t.string("hash");
});
值得注意的是,我在此快速示例中使用了SQLite3,该示例仅支持在插入后返回行ID(因此,在用户插入后,[ userId ]
子句中的then
将被返回。)