Кто-нибудь может объяснить, что я делаю не так с использованием асинхронных функций в 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, оставшуюся стабильной).

Мое понимание функциональности await заключалось в том, что она заставляет асинхронную функцию действовать более или менее синхронно, что, по-видимому, и происходит; код, который я использовал ниже и который использует номер порта, не работает, пока не будет установлен номер порта. (Но даже если это не так, почему переменная 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. Как только файл выбран, клиент просит «присоединиться» к комнате / сеансу. Случай, с которым я сейчас работаю, предполагает, что это новая комната / сеанс, а не попытка сделать какую-то общую комнату / сеанс с другим клиентом. (Да, я знаю, код не очень удобен для совместного использования комнат / сессий, но в настоящее время он работает по большей части и не является моей главной задачей.) Опять же, весь этот код работал нормально до того, как был добавлен асинхронный код (и с использованием переменная статического порта), поэтому я не знаю, что так сильно изменилось, добавив его.

0
Michelle D

2 ответа

Я понял, что после некоторых глупых тестов я попытался сбросить csvFilePath в null вне асинхронного вызова, что и вызывает ошибку ... Упс!

это не имеет ничего общего с async / await . await устранил условия гонки здесь. проблема заключается в объеме вашего варианта csvFilePath . где ты это объявил?

58587110