使用Node.js上运行的MySQL2执行查询时出现ERR_STREAM_WRITE_AFTER_END



我正在实现一个功能,该功能应在单击按钮时通过向后端发送请求并使用MySQL2更新数据库来延长用户会话寿命。

为了做到这一点,我编写了以下前端代码:

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);
}
}

该请求由app.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_AFTR_END]:写入结束后位于Socket.Writeable.write(_stream_writeable.js:297:11)在Connection.write(C:\Users\User\IdeaProjects\TST\node_modules\mysql2\lib\Connection.js:226:17)位于Connection.writePack(C:\Users\User\IdeaProjects\TST\node_modules\mysql2\lib\Connection.js:271:12)在ClientHandshake.sendCredentials(C:\Users\User\IdeaProjects\TST\node_modules\mysql2\lib\commands\client_handshake.js:64:16)在ClientHandshake.handshakeUnit(C:\Users\User\IdeaProjects\TST\node_modules\mysql2\lib\commands\client_handshake.js:137:12)在ClientHandshake.exexecute(C:\Users\User\IdeaProjects\TST\node_modules\mysql2\lib\commands\command.js:39:22)在Connection.handlePacket(C:\Users\User\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)

我还注意到,在调试过程中,我首先在前端得到来自后端的响应,然后才在con.query(qryDict.SQL_QUERIES.setUpdateExtendSession, [session_id, user_id], function (err) {…}中到达DAL中的断点。

我的问题:

  1. 为什么我会得到ERR_STREAM_WRITE_AFTER_END以及如何避免它?

  2. 为什么我首先在前端得到来自后端的响应,然后才到达DAL中的断点?我假设await DAL.sessionExtend(con, session_id, user_id)应该等到DAL上的任务完成并解决承诺。

CherryDT的帮助下,通过切换到ES7async/await-MySQL2-mysql2/promise包装版本,问题已经得到解决。

为了节省其他公众的时间,最后准备使用的代码:

app.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;
},

正如您可能看到的,现在代码是一种更容易理解和维护的方式。

附言:我的建议是:使用async/await,它们非常棒,并尽可能避免回调。

简而言之:您没有等待con.connectioncon.query,因此外部代码继续调用con.close并返回前端结果,稍后con.query尝试通过现已关闭的连接发送查询,导致此异常。

您正在编写异步函数,但只将它们设置为"半异步"。

例如,这不起作用:

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!

因为本质上,异步函数只是同步地调用另一个函数(而不是等待它),然后立即不返回任何结果(即undefined):

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

稍后,您传递的回调将运行,但此时外部代码的列车已经离开平台。

相反,你想做的是让stuff.get返回一个promise(大多数现代库都会这样做,即使它们额外公开了一个回调promise以与旧的代码库兼容),并让await返回它:

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库将公开promise接口,您可以简单地await它。您写道您正在使用mysql2。如果require('mysql2/promise')需要,这个库有一个promise接口。我建议切换到promise接口,而不是回调接口!

还有一种方法可以将现有的con连接"升级"到promise接口: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允许将现有连接升级到promise接口,但无论如何,我都会把它留在这里,以防它对其他处于类似情况的人有帮助

如果你不能切换到promise接口,你可以改为promise现有的调用(不过看起来有点复杂):

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)回调的函数,将其转换为一个异步函数,该函数将返回promise。由于con.query等人是con上的方法,他们需要保留上下文,这就是为什么我写promisify(con.query).call(con, ...)而不仅仅是promisify(con.query)(...)

相关内容

  • 没有找到相关文章

最新更新