Nodejs express и promises не делают то, что я ожидаю

Я пытаюсь создать API для входа в систему с помощью NodeJS, но мой код не выполняет то, что я ожидаю. Я очень новичок в js, promises и все, поэтому, пожалуйста, упростите любой ответ, если это возможно.

Из того, что я вижу на выходе моего кода, первая часть обещания не ждет завершения функции findUsers(...).

У меня есть файл маршрутов, где я хочу выполнить несколько функций последовательно:

  • Найти, если пользователь существует в базе данных
  • если (1 истинно) Хеш и соль введенный пароль
  • ... и т.д.

Файл маршрутов теперь содержит:

var loginM = require('../models/login');
var loginC = require('../controllers/login');
var Promise = require('promise');

module.exports = function(app) {

    app.post('/login/', function(req, res, next) {

        var promise = new Promise(function (resolve, reject) {
            var rows = loginM.findUser(req.body, res);

            if (rows.length > 0) {
                console.log("Success");
                resolve(rows);
            } else {
                console.log("Failed");
                reject(reason);
            }
        });

        promise.then(function(data) {
            return new Promise(function (resolve, reject) {
                loginC.doSomething(data);

                if (success) {
                    console.log("Success 2");
                    resolve(data);
                } else {
                    console.log("Failed 2");
                    reject(reason);
                }
            });
        }, function (reason) {
            console.log("error handler second");
        });
    });
}

И функция findUser содержит объединение и запрос и находится в файле моделей:

var connection = require('../dbConnection');
var loginC = require('../controllers/login');

function Login() {
    var me = this;
    var pool = connection.getPool();

    me.findUser = function(params, res) {
        var username = params.username;

        pool.getConnection(function (err, connection) {
            console.log("Connection ");

            if (err) {
                console.log("ERROR 1 ");
                res.send({"code": 100, "status": "Error in connection database"});
                return;
            }

            connection.query('select Id, Name, Password from Users ' +
                'where Users.Name = ?', [username], function (err, rows) {
                connection.release();
                if (!err) {
                    return rows;
                } else {
                    return false;
                }
            });

            //connection.on('error', function (err) {
            //    res.send({"code": 100, "status": "Error in connection database"});
            //    return;
            //});
        });
    }
}

module.exports = new Login();

Выход, который я получаю:

Server listening on port 3000
Something is happening
error handler second
Connection

То, что я хочу знать об этом коде, двояко:

  • Почему первое обещание не ждет возврата findUser, прежде чем продолжить с if/else и что мне нужно изменить для этого?
  • Почему вывод error handler second, но не Failed?

Я чувствую, что есть что-то, что я полностью недопонимаю о promises. Я благодарен за любой ответ. Спасибо.

Ответ 1

Проблемы с кодом

Хорошо, здесь есть много проблем, поэтому сначала сначала.

        connection.query('...', function (err, rows) {
            connection.release();
            if (!err) {
                return rows;
            } else {
                return false;
            }
        });

Это не сработает, потому что вы возвращаете данные вызывающему абоненту, который является запросом базы данных, который вызывает ваш обратный вызов с помощью err и rows и не заботится о возвращаемом значении вашего обратного вызова.

Что вам нужно сделать, так это вызвать другую функцию или метод, если у вас есть строки или нет.

Вы вызываете:

var rows = loginM.findUser(req.body, res);

и вы ожидаете получить там строки, но вы этого не сделаете. Вы получите undefined, и вы получите его быстрее, чем запрос базы данных даже начался. Он работает следующим образом:

me.findUser = function(params, res) {
    // (1) you save the username in a variable
    var username = params.username;

    // (2) you pass a function to getConnection method
    pool.getConnection(function (err, connection) {
        console.log("Connection ");

        if (err) {
            console.log("ERROR 1 ");
            res.send({"code": 100, "status": "Error in connection database"});
            return;
        }

        connection.query('select Id, Name, Password from Users ' +
            'where Users.Name = ?', [username], function (err, rows) {
            connection.release();
            if (!err) {
                return rows;
            } else {
                return false;
            }
        });

        //connection.on('error', function (err) {
        //    res.send({"code": 100, "status": "Error in connection database"});
        //    return;
        //});
    });

    // (3) you end a function and implicitly return undefined
}

Метод pool.getConnection возвращает сразу после передачи функции до того, как будет выполнено соединение с базой данных. Затем, через некоторое время, эта функция, которую вы передали этому методу, может быть вызвана, но она будет долго после того, как вы уже вернетесь undefined к коду, который хотел получить значение:

var rows = loginM.findUser(req.body, res);

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

Возврат значения является синхронной концепцией и не будет работать для асинхронного кода.


Как promises следует использовать

Теперь, если ваша функция вернула обещание:

me.findUser = function(params, res) {
    var username = params.username;

    return new Promise(function (res, rej) {

      pool.getConnection(function (err, connection) {
        console.log("Connection ");

        if (err) {
          rej('db error');
        } else {
          connection.query('...', [username], function (err, rows) {
            connection.release();
            if (!err) {
                res(rows);
            } else {
                rej('other error');
            }
        });
      });
    });
}

то вы сможете использовать его в какой-либо другой части вашего кода следующим образом:

app.post('/login/', function(req, res, next) {

    var promise = new Promise(function (resolve, reject) {

        // rows is a promise now:
        var rows = loginM.findUser(req.body, res);

        rows.then(function (rowsValue) {
            console.log("Success");
            resolve(rowsValue);
        }).catch(function (err) {
            console.log("Failed");
            reject(err);
        });
    });
    // ...

Описание

Итак, если вы выполняете асинхронную операцию - например, запрос базы данных, то вы не можете сразу иметь значение:

var value = query();

потому что серверу нужно будет блокировать ожидание базы данных до того, как он сможет выполнить назначение, - и это происходит на каждом языке с синхронным блокирующим вводом-выводом (для чего вам необходимо иметь потоки на этих языках, чтобы другие все может быть сделано, пока этот поток заблокирован).

В Node вы можете использовать функцию обратного вызова, которую вы передаете асинхронной функции для вызова, когда она имеет данные:

query(function (error, data) {
  if (error) {
    // we have error
  } else {
    // we have data
  }
});
otherCode();

Или вы можете получить обещание:

var promise = query();
promise.then(function (data) {
  // we have data
}).catch(function (error) {
  // we have error
});
otherCode();

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

Резюме

Вся идея заключается в том, что в асинхронной, неблокирующей, однопоточной среде, такой как Node.JS, вы никогда не делаете больше чем одну вещь за раз, но вы можете дождаться множества вещей. Но вы не просто ждёте чего-то и ничего не делаете, пока ждете, планируете другие вещи, ждете большего, и в итоге вы получите ответ, когда он будет готов.

На самом деле я написал короткую историю на Medium, чтобы проиллюстрировать эту концепцию: Неразрушающий ввод-вывод на планете Asynchronia256/16 - Краткая история, основанная на неопределенных фактах.