【问题标题】:How to keep multiple requests separate in Nodejs / Expressjs如何在 Nodejs / Expressjs 中将多个请求分开
【发布时间】:2018-01-31 19:47:03
【问题描述】:

我正在我的计算机上开发 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

我在运行 Node 8.9.4 和 Express 4.16.2 的 Windows 10 机器上使用 Google Chrome 63 和 jQuery 3.3.1

我的解决方法是链接 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;

    }
};

【问题讨论】:

  • 这实际上是开箱即用的东西,除非你的代码做错了什么。您应该展示您的代码,以便我们帮助调试它。
  • the second request comes in before the first request has been fulfilled JavaScript 是异步执行的。如果您需要更多解释,我们需要查看您的代码
  • 我们能看到代码吗?
  • 不要将requestresponse 存储为全局变量。曾经。在调用它们的范围内处理所有内容,而不是全局处理。
  • @PatrickRoberts 解决了这个问题 - 您应该使用来自 app.post 处理程序的 reqres 并在您的回调链与全局变量中使用它。

标签: javascript node.js express


【解决方案1】:

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

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

是的,有一个单独的请求实例,但没有一个单独的全局或模块级命名空间。因此,当您将 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);
    });
};

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

【讨论】:

  • 这很有帮助。我正在查看您对与承诺相关的其他问题的其他一些答案,并看到您使用了 bluebird 库。假设我添加: const Promise = require("bluebird"),您的示例将如何改变?附言据我了解,bluebird 承诺比 v8 承诺更快,而且通常更好......
  • @DanWillett - 我对我编写的所有异步代码都使用了 Promise。而且,我在最低级别做出承诺,因此在我的逻辑流程中的任何地方都没有简单的回调,只有承诺 - 这要干净得多。如果我正在编写此代码,我将获得一个内置了 Promise 支持的 MySql 库版本,或者我将使用 Bluebird 来承诺整个接口,然后仅使用 Promise 编写此逻辑(看不到普通的回调) .我没有看到此代码的任何其他部分需要 Bluebird 库的扩展功能。
  • @jfriend00 有没有办法从 req 对象中存储一些数据并使用它而不将其显式传递给其他函数?
  • @Durgesh - 这取决于你想要做什么。我们需要更多的上下文。请写下您自己的问题并描述整个问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-09-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多