如何在 Nodejs/Expressjs 中保持多个请求分开

Dan*_*ett 5 javascript node.js express

我正在我的计算机上开发 NodeJS/ExpressJS 应用程序。我在本地运行节点。我有一个单页网络应用程序。当它需要信息时,它会使用 jQuery 发出 ajax 请求。

问题是当我对不同的数据集有多个请求时。Node/express 将开始处理第一个请求,然后第二个请求在第一个请求完成之前进入,它将响应从第一个请求发送到第二个请求,而不是像它假设的那样将它发送到第一个请求。如果我在我的应用程序中暂停(使用警报)以减慢它的速度,因此在第一个请求完成之前不会发送下一个请求,一切正常。

我不明白为什么会这样。我认为 node / express 应该能够处理这个问题并将请求分开。

此外,我在节点中收到“发送后无法设置标头”错误,因为它显然正在合并请求....

这是发生的事情

ajax request 1 -> server
ajax request 2 -> server
ajax request 3 -> server

server -> request1 ( no response )
server -> request2 ( request 1's data)
server -> request3 ( request 2's data)
server for request3 --> error: header's already sent
Run Code Online (Sandbox Code Playgroud)

我在运行 Node 8.9.4 和 Express 4.16.2 的 Windows 10 机器上使用带有 jQ​​uery 3.3.1 的 Google Chrome 63

我的解决方法是链接 ajax 请求,以便在前一个请求收到来自服务器的响应之前不会调用每个请求。但我不应该这样做......

这是相关的服务器代码:

var mysql = require("mysql");
var express = require('express');
var app = express();

var DEBUG = true;

var request = null;
var response = null;

var currentDataRowParser = null;
var con = mysql.createConnection(config);

function ParseMySqlRowData(rowData)
{
    if (DEBUG) console.log("ParseMySqlRowData");

    return JSON.stringify(rowData);
}

var ParseMySqlRowsDatatoResponse = function (err, rows)
{
    if (DEBUG) console.log("ParseMySqlRowsDatatoResponse");

    var MySQLRows;

    try
    {
        if (!err)
        {
            MySQLRows = "[";
            for (var i = 0; i < rows.length; i++)
            {
                if (i > 0)
                    MySQLRows += ", ";

                MySQLRows += currentDataRowParser(rows[i]);
            }

            MySQLRows += "]";

            if (DEBUG) console.log("write rows");
            if (DEBUG) console.log(MySQLRows);
            response.send(MySQLRows);
            response.end();
        }
    }
    catch (ex)
    {
        if (DEBUG) console.log("ParseMySQLRowsDatatoResponse: ERROR");
        if (DEBUG) console.log(ex);
    }
};

var GetQueryData = function (query, dataRowParser, parseDataCallbackFunction)
{
    if (DEBUG) console.log("GetQueryData");

    currentDataRowParser = dataRowParser;

    if (parseDataCallbackFunction == null || parseDataCallbackFunction == undefined)
        parseDataCallbackFunction = ParseDataCallback;

    try
    {
        if (DEBUGQUERY)
        {
            console.log("before query");
            console.log(con.query(query, parseDataCallbackFunction));
            console.log("after query");
            console.log(query.sql);
            DEBUGQUERY = false;
        }
        else
        {
            con.query(query, parseDataCallbackFunction);
        }
    }
    catch (ex)
    {
        console.log(ex);
    }
};

app.post('/getdata', function(req, res)
{
    request = req;
    response = res;
    var query;
    switch (request.body.loaddata)
    {
    case "dataset1":
        query = "SELECT * FROM table1 WHERE key='" + request.body.key + "'";
        GetQueryData(query,ParseMySqlRowData,ParseMySqlRowsDatatoResponse);
        break;
    case "dataset2":
        query = "SELECT * FROM table2 WHERE key='" + request.body.key + "'";
        GetQueryData(query,ParseMySqlRowData,ParseMySqlRowsDatatoResponse);
        break;
    case "dataset3":
        query = "SELECT * FROM table3 WHERE key='" + request.body.key + "'";
        GetQueryData(query,ParseMySqlRowData,ParseMySqlRowsDatatoResponse);
        break;

    }
};
Run Code Online (Sandbox Code Playgroud)

jfr*_*d00 8

您不能将req和存储res在全局或模块级变量中。当第二个请求进来时,它会立即覆盖您的全局变量,并将混合各种请求的数据。

节点不将每个请求实例分开吗?

是的,有一个单独的请求实例,但没有单独的全局或模块级命名空间。因此,当您将 分配req到全局空间时,您正在覆盖前一个,然后您的代码将使用错误的。

将请求和响应作为全局变量非常有帮助。否则我将不得不到处传递它们。

您必须将它们传递给需要它们的较低级别的函数。这就是您将每个请求与其他请求分开的方式。任何需要操作reqres应该传递这些变量的函数,以便它确切地知道要操作哪些变量。

node.js 有一个共享的全局和模块级命名空间。因此,在同一时间进行的所有请求都使用相同的命名空间。应该存储的唯一数据是您特别希望在请求之间共享的数据(例如会话状态)。单个请求或响应对象不应存储在共享变量中。


对您的代码类型进行编码的一种更常见的方法是调用像您这样的函数GetQueryData()并让它返回数据(可能通过承诺),然后在原始请求处理程序中发送响应。然后,您根本不必通过reqres向下传递多个级别。您的辅助函数只是获取数据。请求处理程序获取数据,然后发送响应。它通常是对功能的更好封装。


这是一种按照上述方式重构代码的方法。

  1. GetQueryData() 返回一个满足数据的承诺
  2. ParseMySqlRowsData()只返回解析结果或null解析错误
  3. app.post() 只需获取数据(通过承诺),然后发送适当的响应。
  4. 没有全局reqres没有必要将它们传递到任何地方。

这是代码:

var mysql = require("mysql");
var express = require('express');
var app = express();

var DEBUG = true;

var currentDataRowParser = null;
var con = mysql.createConnection(config);

function ParseMySqlRowData(rowData) {
    if (DEBUG) console.log("ParseMySqlRowData");

    return JSON.stringify(rowData);
}

var ParseMySqlRowsData = function(err, rows) {
    if (DEBUG) console.log("ParseMySqlRowsDatatoResponse");

    var MySQLRows;

    try {
        if (!err) {
            MySQLRows = "[";
            for (var i = 0; i < rows.length; i++) {
                if (i > 0)
                    MySQLRows += ", ";

                MySQLRows += currentDataRowParser(rows[i]);
            }

            MySQLRows += "]";

            if (DEBUG) console.log("write rows");
            if (DEBUG) console.log(MySQLRows);
            return MySQLRows;
        }
    } catch (ex) {
        if (DEBUG) console.log("ParseMySQLRowsDatatoResponse: ERROR");
        if (DEBUG) console.log(ex);
        return null;
    }
};

var GetQueryData = function(query, dataRowParser, parseDataCallbackFunction) {
    return new Promise((resolve, reject) =>{
        if (DEBUG) console.log("GetQueryData");

        let currentDataRowParser = dataRowParser;

        if (parseDataCallbackFunction == null || parseDataCallbackFunction == undefined)
            parseDataCallbackFunction = ParseDataCallback;

        try {
            if (DEBUGQUERY) {
                console.log("before query");
                console.log(con.query(query, parseDataCallbackFunction));
                console.log("after query");
                console.log(query.sql);
                DEBUGQUERY = false;
            } else {
                con.query(query, function(err, rows) {
                    if (err) {
                        reject(err);
                    } else {
                        let result = parseDataCallbackFunction(rows);
                        if (result) {
                            resolve(result);
                        } else {
                            reject(new Error("ParseMySqlRowsData error"));
                        }
                    }
                });
            }
        } catch (ex) {
            console.log(ex);
            reject(new Error("GetQueryData error"));
        }
    });
};

app.post('/getdata', function(req, res) {
    var query;
    let p;
    switch (request.body.loaddata) {
        case "dataset1":
            query = "SELECT * FROM table1 WHERE key='" + request.body.key + "'";
            p = GetQueryData(query, ParseMySqlRowData, ParseMySqlRowsData);
            break;
        case "dataset2":
            query = "SELECT * FROM table2 WHERE key='" + request.body.key + "'";
            p = GetQueryData(query, ParseMySqlRowData, ParseMySqlRowsData);
            break;
        case "dataset3":
            query = "SELECT * FROM table3 WHERE key='" + request.body.key + "'";
            p = GetQueryData(query, ParseMySqlRowData, ParseMySqlRowsData);
            break;
        default:
            p = Promise.reject(new Error("Invalid request.body.loaddata"));
            break;
    }
    p.then(data => {
        res.send(data);
    }).catch(err => {
        console.log(err);
        res.sendStatus(500);
    });
};
Run Code Online (Sandbox Code Playgroud)

PS 我看到你仍然有一个你不应该有的模块级变量:currentDataRowParser. 这需要通过将适当的解析器ParseMySqlRowsData()作为参数传递给来替换,而不是使用模块级共享变量。我将把它作为练习让你解决。用于操作特定请求状态的共享变量在任何服务器编程中都是一个坏主意,特别是在 node.js 编程中。将特定于请求的状态保留在函数参数中reqres对象本身上。这就是您如何防止在处理过程中用另一个请求的数据覆盖来自一个请求的数据。