【问题标题】:How to optimize promises to avoid callback/promise hell如何优化承诺以避免回调/承诺地狱
【发布时间】:2019-09-18 15:02:13
【问题描述】:

我正在为我的数据库使用 knex.js,并且我有一个依赖于前一个查询的查询。

例子:

用户表

|用户名(pk) |名字 |姓氏 |

登录表

|用户名(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 时,我收到一个错误,因为似乎第一个插入(用户)没有完成。

这段代码可以运行,但我知道有更正确的编码方式。

【问题讨论】:

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


    【解决方案1】:

    在 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% 确定您的代码的事实是,您只会回滚最后一个查询,而不是所有查询。密切关注这一点。

    【讨论】:

      【解决方案2】:

      根据我的经验,一旦您不再试图将它们全部塞进同一个函数中,promise 就会变得更加自然! (但我们所有人都可能在某个时候写过类似你的例子的东西,别担心。)

      更小的代码块也倾向于更容易测试和调试。例如,如果您知道您对请求正文中的变量的检查是正确的,那么问题可能就在堆栈的更底层。

      这是一个使用小型中间件堆栈的示例。这允许将操作分成小块,同时仍保证一件事先于另一件事发生。

      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(因此在用户插入后 then 子句中的 [ userId ])。

      【讨论】:

      • 为此加 1 - 像这样将事物切成小块还可以让您(更)轻松地单独测试每个位,因此当事情第一次无法正常工作时,您不会迷失方向。 :)
      • 我唯一关心的是,如何在另一个文件脚本上使用这些小块功能?因为我所做的是,我倾向于将它们分别放在自己的文件中,这样我的主 server.js 就不会变得太大。
      • 你可以把它们放在你喜欢的任何地方。我只是将所有内容都放在一个“文件”中作为示例。因为 Express 中间件始终具有相同的签名,所以您可以编写无聊的旧 JS 函数,这些函数在通过 next 传递控制权之前对 reqres 中的数据进行处理。然后只需导入/要求堆栈中特定路线所需的那些。
      猜你喜欢
      • 1970-01-01
      • 2017-02-09
      • 2014-06-21
      • 2020-12-24
      • 1970-01-01
      • 1970-01-01
      • 2016-06-15
      • 2016-03-28
      • 1970-01-01
      相关资源
      最近更新 更多