Как оптимизировать обещания, чтобы избежать обратного вызова / ада обещаний - PullRequest
0 голосов
/ 30 апреля 2019

Я использую 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));
    }
}

И я не знаю, правильно ли я использую транзакции. Но это то, что я придумал. Раньше, когда я разделяю запрос на два обещания, я получаю сообщение об ошибке, потому что кажется, что первая вставка (пользователь) не заканчивается.

Этот код работает, но я знаю, что есть более правильный способ его кодирования.

Ответы [ 2 ]

2 голосов
/ 30 апреля 2019

По моему опыту, обещания начинают казаться намного более естественными, когда вы перестаете пытаться втиснуть их в одну и ту же функцию! (Но все мы, наверное, когда-то писали что-то похожее на ваш пример, не волнуйтесь.)

Меньшие куски кода имеют тенденцию к тому, чтобы их было проще тестировать и отлаживать. Например, если вы знаете, что ваша проверка переменных в теле запроса верна, то, возможно, проблема заключается еще дальше в стеке.

Вот пример использования небольшого стека промежуточного программного обеспечения. Это позволяет разбить операции на куски размером с кусочек, при этом гарантируя, что одно произойдет раньше другого.

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 для этого быстрого примера, который поддерживает возвращение идентификатора строки только после вставок (отсюда [ userId ] в предложении then после вставки пользователем).

2 голосов
/ 30 апреля 2019

Возврат Обещания внутри обратного вызова затем выполнит обещания одно за другим следующим образом:

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% в вашем коде, является тот факт, что вы будете толькооткатить последний запрос и не все из них.Следите за этим.

...