ERR_STREAM_WRITE_AFTER_END при выполнении запроса с MySQL2, работающим на Node.js - PullRequest
1 голос
/ 14 марта 2020

Я реализую функциональность, которая должна при нажатии на кнопку продлить срок жизни сеанса пользователя, отправив запрос в бэкэнд и обновить базу данных, используя MySQL2 .

In Для этого я написал следующий код переднего плана:

onClose: function (oAction) {
    try {
        if (oAction == "YES") {
            let reqURL = "/sessionExtend";
            let reqData = {
                session_id: sessionStorage.getItem("SessionId"),
                user_id: sessionStorage.getItem("UserId")
            };
            let callbackOK = function (responseData) {
                curr.onSuccessfulResponse(curr, responseData, "sessionExtendSuccess", "sessionExtendFail", "", false);
            };
            let callbackErr = function (responseData) {
                curr.onErrorResponse(curr, responseData, "sessionExtendFail");
            };

            curr.performRequest(reqURL, reqData, callbackOK, callbackErr);
        }
    } catch (err) {
        console.log(err);
        MessageToast.show(sMsg);
    }
}

Запрос получен приложением. js, которое устанавливает соединение с базой данных с использованием MySQL2 и перенаправляет запрос в DAL:

app.post("/sessionExtend", async function (req, res) {

    let session_id = req.body.session_id;
    let user_id = req.body.user_id;

    let con = DAL.getConnection();

    res.setHeader("Content-Type", "application/json");

    try {

        const response = await DAL.sessionExtend(con, session_id, user_id);

        res.send(JSON.stringify({
            "result": true,
            "message": "session extended"
        }));

    } catch (e) {

        res.send(JSON.stringify({
            "result": false,
            "message": "can not extend session"
        }));

    }

    con.close();

});

Модуль DAL выполняет SQL -запрос и должен возвращать результат либо успешно, либо с ошибкой:

sessionExtend: async function sessionExtend(con, session_id, user_id) {

    con.connect(function (err) {
        try {
            if (err) throw err;
            con.query(qryDict.SQL_QUERIES.setUpdateExtendSession, [session_id, user_id], function (err) {

                let result;

                if (err) {
                    result = JSON.stringify({
                        "result": false,
                        "message": "failure"
                    });
                } else {
                    result = JSON.stringify({
                        "result": true,
                        "message": "success"
                    });
                }

                return result;

            });
        } catch (err) {
            let result = JSON.stringify({
                "result": false,
                "message": err
            });

            return result;
        }
    });
},

Проблема в том, что когда я выполняю этот код в отладчике, я получаю исключение:

ERR_STREAM_WRITE_AFTER_END Ошибка [ERR_STREAM_WRITE_AFTER_END]: запись после завершения в Socket.Writable.write (_stream_writable. js: 297: 11) в Connection.write (C: \ Users) Пользователь \ IdeaProjects \ TST \ node_modules \ mysql2 \ lib \ connection. js: 226: 17) в Connection.writePacket (C: \ Users \ Пользователь \ IdeaProjects \ TST \ node_modules \ mysql2 \ lib \ connection. js: 271 : 12) в ClientHandshake.sendCredentials (C: \ Users \ Пользователь \ IdeaProjects \ TST \ node_modules \ mysql2 \ lib \ commands \ client_handshake. js: 64: 16) в ClientHandshake.handshakeInit (C: \ Users \ User \ IdeaProjects \ TST \ node_modules \ mysql2 \ lib \ commands \ client_handshake. js: 137: 12) в ClientHandshake.execute (C: \ Users \ User \ User \ IdeaProjects \ TST \ node_modules \ mysql2 \ lib \ commands \ command. js: 39: 22) в Connection.handlePacket (C: \ Users \ Пользователь \ IdeaProjects \ TST \ node_modules \ mysql2 \ lib \ connection. js: 417: 32) в PacketParser.onPacket ( C: \ Users \ User \ IdeaProjects \ TST \ node_modules \ mysql2 \ lib \ connection. js: 75: 12) в PacketParser.executeStart (C: \ Users \ User \ IdeaProjects \ TST \ node_modules \ mysql2 \ lib \ packet_parser. js: 75: 16) в Socket. (C: \ Users \ User \ IdeaProjects \ TST \ node_modules \ mysql2 \ lib \ connection. js: 82: 25)

Я также обратил внимание, что во время отладки я, во-первых, получить ответный запрос от внешнего интерфейса, и только тогда я достигну точек останова в DAL с помощью con.query(qryDict.SQL_QUERIES.setUpdateExtendSession, [session_id, user_id], function (err) {…}.

Мои вопросы:

  1. Почему я получаю ERR_STREAM_WRITE_AFTER_END и как этого избежать?

  2. Почему я сначала получаю ответ на запрос от внутреннего интерфейса, и только потом я достигаю точки останова в DAL? Я предположил, что await DAL.sessionExtend(con, session_id, user_id) должен подождать, пока задача по DAL будет выполнена и обещание будет выполнено.

Ответы [ 2 ]

2 голосов
/ 14 марта 2020

С помощью CherryDT проблема была решена путем перехода на ES7 async / await версию MySQL2 -обертки - mysql2/promise.

Чтобы сэкономить время остальных пабли c, конечный готовый к использованию код:

приложение. js

app.post("/sessionExtend", async function (req, res) {

    let session_id = req.body.session_id;
    let user_id = req.body.user_id;

    const con = await DAL.getConnection();

    res.setHeader("Content-Type", "application/json");

    const response = await DAL.sessionExtend(con, session_id, user_id);

    res.send(JSON.stringify({
        "result": response.result,
        "message": response.message
    }));

    await con.close();

});

DAL. js

sessionExtend: async function sessionExtend(con, session_id, user_id) {

    let result;

    const [rows, fields] = await con.execute(qryDict.SQL_QUERIES.setUpdateExtendSession, [session_id, user_id]);

    if (rows.warningStatus === 0) {
        result = {
            "result": true,
            "message": "session extended"
        };
    } else {
        result = {
            "result": false,
            "message": "session is not extended"
        };
    }

    return result;

},

Как видите, теперь код стал более понятным и понятным.

PS Мой совет: используйте async / await, они замечательные и старайтесь избегать обратных вызовов в максимально возможной степени.

1 голос
/ 14 марта 2020

Короче говоря : вы не ожидаете con.connection и con.query, поэтому внешний код продолжает и вызывает con.close и возвращает результат внешнего интерфейса, а затем con.query пытается отправить запрос через закрытое соединение, приводящее к этому исключению.

Вы пишете асин c функций, но вы сделали их только "полусинными c".

Например, это не сработает:

async function getStuff () {
  stuff.get(function (err, data) {
    if (err) throw err // kills your process if it happend!
    return data.stuff // returns to nowhere
  })
}

// later on:
const stuff = await getStuff()
console.log(stuff) // prints undefined!

... потому что, по сути, ваша асин c функция просто синхронно вызывает другую функцию (не ждет ее), а затем сразу ничего не возвращает (т.е. undefined):

async function getStuff () {
  stuff.get(...)
  // as you can see, no return inside getStuff
}

И позже, обратный вызов, который вы прошли, будет запущен, но затем поезд вашего внешнего кода уже покинул платформу.

Вместо этого вы должны иметь stuff.get вернуть обещание (большинство современных библиотек сделают это, даже если они дополнительно предоставляют обещание обратного вызова для совместимости со старыми кодовыми базами) и await it:

async function getStuff () {
  const data = await stuff.get() // waits for the stuff to come back
  return data.stuff // actually returns the stuff
  // The `if (err) throw err` now became unnecessary as well
}

// later on:
const stuff = await getStuff()
console.log(stuff) // prints the stuff!

Если ваша библиотека SQL предоставит обещание интерфейс, вы могли бы просто await это. Вы написали, что используете mysql2. Эта библиотека имеет интерфейс обещаний, если требуется с require('mysql2/promise'). Я бы предложил переключиться на интерфейс обещания вместо интерфейса обратного вызова!

Существует также способ «обновить» существующее con соединение с интерфейсом обещания: con.promise(). Таким образом, вы просто сделаете con = DAL.getConnection().promise() вместо con = DAL.getConnection().

Затем вы можете переписать код следующим образом (или эквивалентным, в зависимости от того, какую библиотеку вы выберете):

async function sessionExtend(con, session_id, user_id) {
    try {
        await con.connect()
        await con.query(qryDict.SQL_QUERIES.setUpdateExtendSession, [session_id, user_id])
        return JSON.stringify({ result: true, message: 'success' })
    } catch (err) {
        return JSON.stringify({ result: false, message: err.toString() })
    }
}

РЕДАКТИРОВАТЬ: Следующая часть фактически устарела, потому что mysql2 позволяет обновить существующее соединение с интерфейсом обещаний, но я все равно оставлю это здесь на случай, если это поможет кому-то еще в похожая ситуация!

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

const { promisify } = require('util')

async function sessionExtend(con, session_id, user_id) {
    try {
        await promisify(con.connect).call(con)
        await promisify(con.query).call(con, qryDict.SQL_QUERIES.setUpdateExtendSession, [session_id, user_id])
        return JSON.stringify({ result: true, message: 'success' })
    } catch (err) {
        return JSON.stringify({ result: false, message: err.toString() })
    }
}

util.promisify оборачивает функцию, которая ожидает обратный вызов (err, data), преобразуя ее в asyn c функцию, которая вместо этого возвращает обещание. Поскольку con.query и соавт. это методы для con, они должны сохранять этот контекст, поэтому я написал promisify(con.query).call(con, ...) вместо promisify(con.query)(...).

...