如何优化承诺以避免回调/承诺地狱

时间:2019-04-30 01:05:26

标签: node.js express promise callback knex.js

我正在为数据库使用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时,我收到一个错误,因为似乎第一个插入(用户)未完成。

此代码有效,但我知道有一种更正确的编码方法。

2 个答案:

答案 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将被返回。)