Reputation: 260
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.
Upvotes: 3
Views: 3943
Reputation: 260
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();
}
Upvotes: 0
Reputation: 17579
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.
Upvotes: 2
Reputation: 2771
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.
Upvotes: 2