3

I'm trying to write a script that asks three questions in a row, while waiting for user input in between each question.
It seems that I have difficulty understanding how to do this with the non-blocking nature of node.
Here's the code I'm running:

var shell = require('shelljs/global'),
    utils     = require('./utils'),
    readline  = require('readline'),
    fs        = require('fs'),
    path      = require('path');

var rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
});    

setUserDefaultSettings();

function setUserDefaultSettings() {
    var defaultSettingsFile = find('DefaultSettings.json');
    var defaultSettingsJSON = [
        {
            macro: '__new_project_path__',
            question: 'Please enter the destination path of your new project: '
        },
        {
            macro: '__as_classes_path__',
            question: 'Please enter the path to your ActionScript Classes: ',
        },
        {
            macro: '__default_browser_path__',
            question: 'Please enter the path to your default browser: '

        }
    ];
    var settingsKeys = [];
    var index        = 0;

    if (!test('-f', 'UserSettings.json')) {
        cp(defaultSettingsFile, 'UserSettings.json');
    }

    var userSettingsFile = pwd() + path.sep + find('UserSettings.json');

    fs.readFile(userSettingsFile, 'utf8', function (err, data) {
        if (err) {
            echo('Error: ' + err);
            return;
        }
        data = JSON.parse(data);

        for(var attributename in data) {
            settingsKeys.push(attributename);
        }

        defaultSettingsJSON.forEach(function(key) {
            index++;
            // Check if macros have been replaced
            if (data[settingsKeys[index - 1]] === key.macro) {
                // Replace macros with user input.
                replaceSettingMacro(userSettingsFile, key.macro, key.question);
            }
        });
    });
}

function replaceSettingMacro(jsonFile, strFind, question) {
    askUserInput(question, function(strReplace) {
        sed('-i', strFind, strReplace, jsonFile);
    });
}

function askUserInput(string, callback) {
    rl.question(string, function(answer) {
        fs.exists(answer, function(exists) {
            if (exists === false) {
                echo('File ' + answer + ' not found!');
                askUserInput(string, callback);
            } else {
                callback(answer);
            }
        });
    });
}

Only the first question is asked, as the script continues execution while the user is inputting their answer. I understand why this is the case, but don't know how I can work around this.

1
  • Have you tried callbacks? Commented Apr 3, 2014 at 21:41

3 Answers 3

2

This looks like an appropriate place to implement a queue. The queue will initiate tasks one at a time only starting the next after the previous has finished.

in your askUserInput method try something like:

var questionQueue = [];
function askUserInput(string, callback) {
    if(questionQueue.length == 0) {
        rl.question(string, function(answer) {
            fs.exists(answer, function(exists) {
                if (exists === false) {
                    echo('File ' + answer + ' not found!');
                    askUserInput(string, callback);
                } else {
                    callback(answer);

                    if(questionQueue.length > 0){
                        var question questionQueue.shift();
                        askUserInput(question.string, question.callback);
                    }
                }
            });
        });
    }
    else {
        questionQueue.push({string: string, callback: callback});
    }
}

Another option included extending your callback further up the call stack and invoke the next question when you receive the callback from the previous.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks, I've managed to find the answer while following your advice on queueing and callbacks. I've just wrote down my solution as a post.
2

The 2 ways I'd handle this:
a) use sync-prompt - would make it very easy

If you still wanted to handle it asynchronously,
b) I'd use a promise library such as When and do a When.all([array of promises]) before continuing.

1 Comment

I've found the module and the library really interesting, useful and easy to use. Definitely something I will use in the future. However, for the purpose of this question, I believe that the correct workaround was the one cited by Preston-S.
0

Below is the correct solution to my question, following @Preston-S advice:

var questionList = [];
var macroList    = [];

setUserDefaultSettings();

function setUserDefaultSettings() {
    var defaultSettingsFile = find('DefaultSettings.json');
    var defaultSettingsJSON = [
        {
            macro: '__new_project_path__',
            question: 'Please enter the destination path of your new project: '
        },
        {
            macro: '__as_classes_path__',
            question: 'Please enter the path to your ActionScript Classes: ',
        },
        {
            macro: '__default_browser_path__',
            question: 'Please enter the path to your default browser: '

        }
    ];

    if (!test('-f', 'UserSettings.json')) {
        cp(defaultSettingsFile, 'UserSettings.json');
    }

    userSettingsFile = pwd() + path.sep + find('UserSettings.json');

    var settingsKeys     = [];
    var index            = 0;
    var canAskQuestion   = false;

    fs.readFile(userSettingsFile, 'utf8', function (err, data) {
        if (err) {
            echo('Error: ' + err);
            return;
        }
        data = JSON.parse(data);

        for(var attributename in data) {
            settingsKeys.push(attributename);
        }

        defaultSettingsJSON.forEach(function(key) {
            index++;
            // Check if macros have been replaced
            if (data[settingsKeys[index - 1]] === key.macro) {
                // Replace macros with user input.
                questionList.push(key.question);
                macroList.push(key.macro);

                if (!canAskQuestion) {
                    askQuestion(function() {
                        copyTemplate();
                    });
                    canAskQuestion = true;
                }
            } else {
                copyTemplate();
            }
        });
    });
}

function askQuestion(callback) {
    replaceSettingMacro(userSettingsFile, macroList.shift(), questionList.shift(), function() {
        if (macroList.length < 1 || questionList.length < 1) {
            callback();
        } else {
            askQuestion(callback);
        }
    });
}

function replaceSettingMacro(jsonFile, strFind, question, callback) {
    askUserInput(question, function(strReplace) {
        sed('-i', strFind, strReplace, jsonFile);
        callback();
    });
}

function askUserInput(string, callback) {
    rl.question(string, function(answer) {
        fs.exists(answer, function(exists) {
            if (exists === false) {
                echo('File ' + answer + ' not found!');
                askUserInput(string, callback);
            } else {
                callback(answer);
            }
        });
    });
}

function copyTemplate() {
    rl.close();
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.