【问题标题】:Javascript Await Changes Local Variables? [closed]Javascript Await 更改局部变量? [关闭]
【发布时间】:2019-10-28 07:33:35
【问题描述】:

谁能解释我在 Javascript 中使用异步函数时做错了什么?

基本上,我必须在我的 Node.js 代码中使用异步来获取一个开放端口供我使用。在异步调用之外设置了一个局部变量,我可以很好地访问/使用它,直到我等待异步函数返回。之后,局部变量未定义。

(async () => {
    console.log("CHECK AFTER ASYNC1: " + csvFilePath);
    // First, grab a valid open port
    var port;
    while (!port || portsInProcess.indexOf(port) >= 0) {
        console.log("CHECK AFTER ASYNC2: " + csvFilePath);
        port = await getPort();
        console.log(port);
    }
    console.log("CHECK AFTER ASYNC3: " + csvFilePath);
    portsInProcess.push(port);
    // ... more code below...

检查 #1 和 2 对 csvFilePath 变量很好,但检查 #3 表明它是未定义的。但是,端口号很好。这让我相信 Javascript 中的异步函数调用有一些奇怪之处,它只会影响局部变量;我进一步使用的全局变量很好。不幸的是,我无法将 csvFilePath 变量设为全局变量,因为这也会在该变量上引入竞争条件(我在其他地方阻止了这种情况;while 循环有助于防止端口号上的竞争条件,这在我的简单测试中基本上没有使用在本地主机上)。

以防万一,这是我得到的输出:

CHECK AFTER ASYNC1: data/text/crescent_topics.csv
CHECK AFTER ASYNC2: data/text/crescent_topics.csv
58562
CHECK AFTER ASYNC3: null

可能还值得一提的是,实际上只有前几行代码可以动态获取开放端口,这就是我添加的代码行。我之前使用固定端口号的代码工作得很好(包括这个 csvFilePath 变量保持稳定)。

我对@9​​87654325@ 功能的理解是,它使异步函数或多或少地同步运行,这似乎是这里发生的事情;在设置端口号之前,我使用端口号的代码不会运行。 (但即使不是这样,为什么 csvFilePath 变量没有设置,因为我没有在这里更改或以任何方式使用它?)

编辑:这里还有一些代码可以提供额外的上下文

var spawn = require('child_process').spawn;
var fs = require("fs");
var async = require('async');
var zmq = require('zmq');
var readline = require('readline');
const getPort = require('get-port');

/* Export the Nebula class */
module.exports = Nebula;

/* Location of the data for the Crescent dataset */
var textDataPath = "data/text/";
var crescentRawDataPath = textDataPath + "crescent_raw";
var crescentTFIDF = textDataPath + "crescent tfidf.csv";
var crescentTopicModel = textDataPath + "crescent_topics.csv";

/* Location of the data for the UK Health dataset */
var ukHealthRawDataPath = textDataPath + "uk_health_raw";
var ukHealthTFIDF = textDataPath + "uk_health.csv";

/* Map CSV files for text data to raw text location */
var textRawDataMappings = {};
textRawDataMappings[crescentTFIDF] = crescentRawDataPath;
textRawDataMappings[crescentTopicModel] = crescentRawDataPath;
textRawDataMappings[ukHealthTFIDF] = ukHealthRawDataPath;
textRawDataMappings[textDataPath + "uk_health_sm.csv"] = ukHealthRawDataPath;

/* The pipelines available to use */
var flatTextUIs = ["cosmos", "composite", "sirius", "centaurus"];
var pipelines = {
    andromeda: { 
        file: "pipelines/andromeda.py",
        defaultData: "data/highD/Animal_Data_study.csv"
     },
     cosmos: {
        file: "pipelines/cosmos.py",
        defaultData: textDataPath + "crescent tfidf.csv"
     },
     sirius: {
        file: "pipelines/sirius.py",
        defaultData: "data/highD/Animal_Data_paper.csv"
     },
     centaurus: {
        file: "pipelines/centaurus.py",
        defaultData: "data/highD/Animal_Data_paper.csv"
     },
     twitter: {
        file: "pipelines/twitter.py",
     },
     composite: {
        file: "pipelines/composite.py",
        defaultData: textDataPath + "crescent tfidf.csv"
     },
     elasticsearch: {
        file: "pipelines/espipeline.py",
        args: []
     }
};

/* The locations of the different types of datasets on the server */
var textDataFolder = "data/text/";
var highDDataFolder = "data/highD/";
var customCSVFolder = "data/customCSV/";

var sirius_prototype = 2;

// An array to track the ports being processed to eliminate race conditions
// as much as possible
var portsInProcess = [];

var nextSessionNumber = 0;
var usedSessionNumbers = [];

/* Nebula class constructor */
function Nebula(io, pipelineAddr) {
    /* This allows you to use "Nebula(obj)" as well as "new Nebula(obj)" */
    if (!(this instanceof Nebula)) { 
        return new Nebula(io);
    }

    /* The group of rooms currently active, each with a string identifier
     * Each room represents an instance of a visualization that can be shared
     * among clients.
     */
    this.rooms = {};
    this.io = io;

    /* For proper use in callback functions */
    var self = this;

    /* Accept new WebSocket clients */
    io.on('connection', function(socket) {

    // Skipped some irrelevant Socket.io callbacks

    **// Use the csvFilePath to store the name of a user-defined CSV file
        var csvFilePath = null;**

        /* Helper function to tell the client that the CSV file is now ready for them
        * to use. They are also sent a copy of the data
        */
        var csvFileReady = function(csvFilePath) {

            // Let the client know that the CSV file is now ready to be used on
            // the server
            socket.emit("csvDataReady");

            // Prepare to parse the CSV file
            var csvData = [];
            const rl = readline.createInterface({
                input: fs.createReadStream(csvFilePath),
                crlfDelay: Infinity
            });

            // Print any error messages we encounter
            rl.on('error', function (err) {
                console.log("Error while parsing CSV file: " + csvFilePath);
                console.log(err);
            });

            // Read each line of the CSV file one at a time and parse it
            var columnHeaders = [];
            var firstColumnName;
            rl.on('line', function (data) {                
                var dataColumns = data.split(",");

                // If we haven't saved any column names yet, do so first
                if (columnHeaders.length == 0) {
                    columnHeaders = dataColumns;
                    firstColumnName = columnHeaders[0];
                }

                // Process each individual line of data in the CSV file
                else {
                    var dataObj = {};
                    var i;
                    for (i = 0; i < dataColumns.length; i++) {
                        var key = columnHeaders[i];
                        var value = dataColumns[i];
                        dataObj[key] = value
                    }
                    csvData.push(dataObj);
                }

            });

            // All lines are read, file is closed now.
            rl.on('close', function () {

                // On certain OSs, like Windows, an extra, blank line may be read
                // Check for this and remove it if it exists
                var lastObservation = csvData[csvData.length-1];
                var lastObservationKeys = Object.keys(lastObservation);
                if (lastObservationKeys.length = 1 && lastObservation[lastObservationKeys[0]] == "") {
                    csvData.pop();
                }

                // Provide the CSV data to the client
                socket.emit("csvDataReadComplete", csvData, firstColumnName);
            });
        };

        **/* Allows the client to specify a CSV file already on the server to use */
        socket.on("setCSV", function(csvName) {
            console.log("setCSV CALLED");
            csvFilePath = "data/" + csvName;
            csvFileReady(csvFilePath);
            console.log("CSV FILE SET: " + csvFilePath);
        });**

        // Skipped some more irrelevant callbacks

        /*  a client/ a room. If the room doesn't next exist yet,
        * initiate it and send the new room to the client. Otherwise, send
        * the client the current state of the room.
        */
        socket.on('join', function(roomName, user, pipeline, args) {
            console.log("Join called for " + pipeline + " pipeline; room " + roomName);
            socket.roomName = roomName;
            socket.user = user;
            socket.join(roomName);

            console.log("CSV FILE PATH: " + csvFilePath);

            var pipelineArgsCopy = [];

            if (!self.rooms[roomName]) {
                var room = {};
                room.name = roomName;
                room.count = 1;
                room.points = new Map();
                room.similarity_weights = new Map();

                if (pipeline == "sirius" || pipeline == "centaurus") {
                    room.attribute_points = new Map();
                    room.attribute_similarity_weights = new Map();
                    room.observation_data = [];
                    room.attribute_data = [];
                }

                /* Create a pipeline client for this room */
                console.log("CHECK BEFORE ASYNC: " + csvFilePath);
                **// Here's the code snippet I provided above**
                **(async () => {
                    console.log("CHECK AFTER ASYNC1: " + csvFilePath);
                    // First, grab a valid open port
                    var port;
                    while (!port || portsInProcess.indexOf(port) >= 0) {
                        console.log("CHECK AFTER ASYNC2: " + csvFilePath);
                        port = await getPort();
                        console.log(port);
                    }
                    console.log("CHECK AFTER ASYNC3: " + csvFilePath);**
                    portsInProcess.push(port);
                    console.log("CHECK AFTER ASYNC4: " + csvFilePath);

                    if (!pipelineAddr) {
                        var pythonArgs = ["-u"];
                        if (pipeline in pipelines) {

                            // A CSV file path should have already been set. This
                            // file path should be used to indicate where to find
                            // the desired file
                            console.log("LAST CHECK: " + csvFilePath);
                            if (!csvFilePath) {
                                csvFilePath = pipelines[pipeline].defaultData;
                            }
                            console.log("FINAL CSV FILE: " + csvFilePath);
                            pipelineArgsCopy.push(csvFilePath);

                            // If the UI supports reading flat text files, tell the
                            // pipeline where to find the files
                            if (flatTextUIs.indexOf(pipeline) >= 0) {
                                pipelineArgsCopy.push(textRawDataMappings[csvFilePath]);
                            }

                            // Set the remaining pipeline args
                            pythonArgs.push(pipelines[pipeline].file);
                            pythonArgs.push(port.toString());
                            if (pipeline != "twitter" && pipeline != "elasticsearch") {
                                pythonArgs = pythonArgs.concat(pipelineArgsCopy);
                            }
                        }
                        else {
                            pythonArgs.push(pipelines.cosmos.file);
                            pythonArgs.push(port.toString());
                            pythonArgs.push(pipelines.cosmos.defaultData);
                            pythonArgs.push(crescentRawDataPath);
                        }

                        // used in case of CosmosRadar
                        for (var key in args) {
                            if (args.hasOwnProperty(key)) {
                                pythonArgs.push("--" + key);
                                pythonArgs.push(args[key]);
                            }
                        }

                        // Dynamically determine which distance function should be
                        // used
                        if (pythonArgs.indexOf("--dist_func") < 0) {
                            if (pipeline === "twitter" || pipeline === "elasticsearch" ||
                                    csvFilePath.startsWith(textDataPath)) {
                                pythonArgs.push("--dist_func", "cosine");
                            }
                            else {
                                pythonArgs.push("--dist_func", "euclidean");
                            }
                        }

                        console.log(pythonArgs);
                        console.log("");

                        var pipelineInstance = spawn("python2.7", pythonArgs, {stdout: "inherit"});

                        pipelineInstance.on("error", function(err) {
                            console.log("python2.7.exe not found. Trying python.exe");
                            pipelineInstance = spawn("python", pythonArgs,{stdout: "inherit"});

                            pipelineInstance.stdout.on("data", function(data) {
                                console.log("Pipeline: " + data.toString());
                            });
                            pipelineInstance.stderr.on("data", function(data) {
                                console.log("Pipeline error: " + data.toString());
                            });
                        });

                        /* Data received by node app from python process, 
                         * ouptut this data to output stream(on 'data'), 
                         * we want to convert that received data into a string and 
                         * append it to the overall data String
                         */
                        pipelineInstance.stdout.on("data", function(data) {
                            console.log("Pipeline STDOUT: " + data.toString());
                        });
                        pipelineInstance.stderr.on("data", function(data) {
                            console.log("Pipeline error: " + data.toString());
                        });

                        room.pipelineInstance = pipelineInstance;
                    }

                    /* Connect to the pipeline */
                    pipelineAddr = pipelineAddr || "tcp://127.0.0.1:" + port.toString();

                    room.pipelineSocket = zmq.socket('pair');
                    room.pipelineSocket.connect(pipelineAddr);

                    pipelineAddr = null;
                    portsInProcess.splice(portsInProcess.indexOf(port), 1);

                    /* Listens for messages from the pipeline */
                    room.pipelineSocket.on('message', function (msg) {
                        self.handleMessage(room, msg);
                    });

                    self.rooms[roomName] = socket.room = room;
                    invoke(room.pipelineSocket, "reset");
                })();
            }
            else {
                socket.room = self.rooms[roomName];
                socket.room.count += 1;

                if (pipeline == "sirius" || pipeline == "centaurus") {
                    socket.emit('update', sendRoom(socket.room, true), true);
                    socket.emit('update', sendRoom(socket.room, false), false);
                }
                else {
                    socket.emit('update', sendRoom(socket.room));
                }
            }

            // Reset the csvFilePath to null for future UIs...
            // I don't think this is actually necessary since 
            // csvFilePath is local to the "connections" message,
            // which is called for every individual room
            csvFilePath = null;
        });

        // Skipped the rest of the code; it's irrelevant
    });
}

完整的打印输出:

setCSV CALLED
CSV FILE SET: data/text/crescent_topics.csv
Join called for sirius pipeline; room sirius0
CSV FILE PATH: data/text/crescent_topics.csv
CHECK BEFORE ASYNC: data/text/crescent_topics.csv
CHECK AFTER ASYNC1: data/text/crescent_topics.csv
CHECK AFTER ASYNC2: data/text/crescent_topics.csv
58562
CHECK AFTER ASYNC3: null
CHECK AFTER ASYNC4: null
LAST CHECK: null
FINAL CSV FILE: data/highD/Animal_Data_paper.csv
[ '-u',
  'pipelines/sirius.py',
  '58562',
  'data/highD/Animal_Data_paper.csv',
  undefined,
  '--dist_func',
  'euclidean' ]

由于代码加粗不起作用,只需搜索“**”即可找到我标记的相关部分。

TL;DR 客户端和服务器之间发生了大量通信,以建立直接链接到特定数据集的个性化通信。用户可以将自定义 CSV 文件上传到系统,但我现在使用的代码只是试图选择服务器上现有的 CSV 文件,因此我省略了自定义 CSV 文件的回调。一旦选择了文件,客户就会要求“加入”一个房间/会话。我现在正在处理的案例假设这是一个新的房间/会话,而不是尝试与另一个客户进行一些共享的房间/会话。 (是的,我知道,共享房间/会话的代码很乱,但它现在大部分时间都有效,不是我主要关心的问题。)再次,所有这些代码在添加异步代码之前工作得很好(并使用一个静态端口变量),所以我不知道添加它有什么变化。

【问题讨论】:

  • 从问题中的代码来看,听起来getPort 正在重新分配csvFilePath,尽管这听起来很奇怪——如果这不是正在发生的事情,那么问题中的代码缺少一些你脚本中的代码有。需要minimal reproducible example 来确定
  • cvsFilePath 很可能在它是 await 期间被你的函数之外的东西重新分配,从而弄乱了这个函数。一个简单的规则是不要使用可以在异步操作内部修改的共享变量,因为当它们异步执行它们的操作并等待完成时,其他东西可能会运行并更改这些共享变量。可能你应该将它们作为参数传递给这个异步函数,然后它们就不会从你下面被改变。
  • 如果您想要更精确的路径,您需要向我们展示更多代码,以便我们查看cvsFIlePath 的定义位置、它的使用方式、还有什么可以改变它等等...
  • 现在您已经添加了其余代码,我可以确切地看到发生了什么,并在下面的答案中进行了解释。

标签: javascript node.js asynchronous async-await port


【解决方案1】:

由于您现在包含了整个代码上下文,我们可以看到问题在于您的 async IIFE 之后的代码是导致问题的原因。

async 函数在遇到await 时立即返回一个承诺。而且,当await 正在等待其异步操作时,调用async 函数之后的代码会运行。在您的情况下,您实际上是在这样做:

var csvFilePath = someGoodValue;

(async () => {
     port = await getPort();
     console.log(csvFilePath);    // this will be null
})();

csvFilePath = null;               // this runs as soon as the above code hits the await

因此,一旦您点击了第一个 awaitasync 函数就会返回一个 Promise,并且它后面的代码会继续运行,点击重置您的 csvFilePath 的代码行。


可能有更简洁的方法来重构您的代码,但您可以做的一个简单的事情是:

var csvFilePath = someGoodValue;

(async () => {
     port = await getPort();
     console.log(csvFilePath);    // this will be null
})().finally(() => {
    csvFilePath = null;
});

注意:节点 v10+ 支持 .finally()。如果您使用的是旧版本,您可以重置.then().catch() 中的路径。

或者,正如您的评论所说,也许您可​​以完全删除 csvFilePath 的重置。

【讨论】:

  • 我昨天意识到我的错误,但你的回答提供了比我想象的更有用的细节和其他解决问题的方法。谢谢!! :)
【解决方案2】:

经过一些愚蠢的测试后,我意识到我在异步调用之外将csvFilePath 重置为null,这就是导致错误的原因...哎呀!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-30
    • 1970-01-01
    相关资源
    最近更新 更多