重复输出到控制台 - 我需要在哪里插入我的回调函数?

4
这是我目前的代码:

这是我的代码:

var databaseUrl = "mydb";
var collections = ["users", "reports"];
var db = require("mongojs").connect(databaseUrl, collections);

newuser =       {
            email: "john@example.com",
            password: "iLoveMongo",
            sex: "male"
        };


saveuser = function(user, callback) {
    db.users.save(
        user,
        function( error, saved) {
            if( error || !saved )
                console.log( "User not saved");
            else 
                console.log( "User saved" );
                callback();
        }
    );
};

removeuser = function() {
    db.users.remove(
        {
            sex: "male"
        },
        function( error, removed)   {
            if( error || !removed )
                console.log( "User not deleted");
            else 
                console.log( "User deleted" );
        }
    );
};

finduser = function() {
    db.users.find(
        {
            sex: "male"
        },
        function(error, users) {
            if( error || !users.length )
                console.log( "No male Users found");
            else 
            {
                console.log( "male Users found" );
                users.forEach(
                    function(maleUser) {
                        console.log(maleUser);
                    }
                );
            }
        }
    )
};

updateuser = function() {
    db.users.update(
        {
            email: "john@example.com"
        },
        {
            $set: { password: "iReallyLoveMongo" }
        },
        function(err, updated) {
            if( err || !updated )
                console.log("User not updated");
            else
                console.log("User updated");
        }
    );  
}

var options = {
    1: { 
        option: "Save a User",
        execute: saveuser.bind(null, newuser, finduser)
    },
    2: { 
        option: "Remove a User",
        execute: removeuser
    },
    3: { 
        option: "Find a User",
        execute: finduser
    },
    4: { 
        option: "Update a User",
        execute: updateuser
    }
}
function read() {
    console.log("");
    console.log(" Enter your choice: ");

    stdin = process.stdin;
    stdin.setEncoding('utf8');
    stdin.on('data', choice);
};

function choice (data) {
    data = parseInt(data);

    console.log("You entered: " + data);
    console.log("You choose to do: " + options[data]["option"]);
    options[data]["execute"]();
};

read();

第一次输入命令时会弹出“请输入您的选择”文本,但您仍然可以输入数字并执行其他选项。

这是在控制台上看到的:

PS C:\Users\Benni\Documents\nodejs> node .\mongotest\app.js

 Enter your choice:
1
You entered: 1
You choose to do: Save a User
User saved
male Users found
{ _id: 5386ba18463a008011fe6213,
  email: 'john@example.com',
  password: 'iLoveMongo',
  sex: 'male' }
3
You entered: 3
You choose to do: Find a User
male Users found
{ _id: 5386ba18463a008011fe6213,
  email: 'john@example.com',
  password: 'iLoveMongo',
  sex: 'male' }

我希望的是,在每个成功的命令执行完之后(即options[data]["execute"]()完成之后),能够再次弹出“输入您的选择”的提示。就像这样:
PS C:\Users\Benni\Documents\nodejs> node .\mongotest\app.js

 Enter your choice:
1
You entered: 1
You choose to do: Save a User
User saved
male Users found
{ _id: 5386ba18463a008011fe6213,
  email: 'john@example.com',
  password: 'iLoveMongo',
  sex: 'male' }

 Enter your choice:
3
You entered: 3
You choose to do: Find a User
male Users found
{ _id: 5386ba18463a008011fe6213,
  email: 'john@example.com',
  password: 'iLoveMongo',
  sex: 'male' }

我应该在哪里再次调用read()?如果我把它放在choice的末尾,"输入你的选择"会在options[data]["execute"]();完成之前弹出。如果我把它放在read的末尾,我会得到一个无限循环/超过最大调用堆栈大小的错误。
或者我可以为options[data]["execute"]();添加回调函数吗?类似于:
function( null, read){
  options[data]["execute"]();
};

?

3个回答

1
你面临的基本问题是,在数据库方法完成后应该显示消息。你通过回调报告完成情况,但是问题在于,并非所有方法都提供回调来确定它们何时完成。即使有回调,当你调用方法时也没有注册回调。因此,你应该在所有函数中定义一个回调参数,然后在读取期间注册回调:
newuser =       {
            email: "john@example.com",
            password: "iLoveMongo",
            sex: "male"
        };


saveuser = function(user, callback) {
    db.users.save(
        user,
        function( error, saved) {
            if( error || !saved )
                callback( "User not saved");
            else                 
                callback(saved);
        }
    );
};

removeuser = function(callback) {
    db.users.remove(
        {
            sex: "male"
        },
        function( error, removed)   {
            if( error || !removed )
                callback( "User not deleted");
            else 
                console.log( "User deleted" );
                callback();
        }
    );
};

finduser = function(callback) {
    db.users.find(
        {
            sex: "male"
        },
        function(error, users) {
            if( error || !users.length )
                callback( "No male Users found");
            else 
            {
                console.log( "male Users found" );
                callback(null, users);
            }
        }
    )
};

updateuser = function(callback) {
    db.users.update(
        {
            email: "john@example.com"
        },
        {
            $set: { password: "iReallyLoveMongo" }
        },
        function(err, updated) {
            if( err || !updated )
                callback("User not updated");
            else
                console.log("User updated");
                callback(null, updated);
        }
    );  
}

var options = {
    1: { 
        option: "Save a User",
        execute: saveuser.bind(null, newuser)
    },
    2: { 
        option: "Remove a User",
        execute: removeuser
    },
    3: { 
        option: "Find a User",
        execute: finduser
    },
    4: { 
        option: "Update a User",
        execute: updateuser
    }
}
function read() {
    console.log("");
    console.log(" Enter your choice: ");

    stdin = process.stdin;
    stdin.setEncoding('utf8');
    stdin.on('data', choice);
};

function choice (data) {
    data = parseInt(data);

    console.log("You entered: " + data);
    console.log("You choose to do: " + options[data]["option"]);
    options[data]["execute"](function(err, res){
        if(err){
            console.log(err);                
        }
        if(res){
            if(Array.isArray(res)){
                res.forEach(function(item){
                    console.log(item);
                })
            } else {
                console.log(res);
            }
        }
        console.log(" Enter your choice: ");
    });
};

read();

使用 Promises

对于这种情况的另一种解决方案是使用 Promises。这样,您对数据库的调用可以返回一个 Promise,您可以稍后使用它来等待适当的响应。

我在这个答案中使用了 Q Promises,但您可以使用任何其他类似的库。您可以在此处看到如何为您的项目获取 Q。

我们可以先确保您的数据库服务返回一些内容,在这种情况下是一个 Promise。为此,我将使用 Q.ninvoke 将基于回调的函数转换为 Promise。

var Q = require('q');
saveUser = function(user) {
    return Q.ninvoke(db.users,"save", user)
        .then(function(user){
            console.log("User saved: %j", user);
        })
        .catch(function(error){
            console.log("Error while saving: %s", error);
        });
};

removeUser = function() {
    return Q.ninvoke(db.users,"remove", {sex: "male"})
        .then(function(result){
            console.log("User removed: %j", result);
        })
        .catch(function(error){
            console.log("Error while removing: %s", error);
        });
};

findUser = function() {
    return Q.ninvoke(db.users,"find", {sex: "male"})
        .then(function(user){
            console.log("User found: %j", user);
        })
        .catch(function(error){
            console.log("Error while finding: %s", error);
        });
};

updateUser = function() {
    return Q.ninvoke(db.users,"update", 
        {
            email: "john@example.com"
        },
        {
            $set: { password: "iReallyLoveMongo" }
        })
        .then(function(user){
            console.log("User updated: %j", user);
        })
        .catch(function(error){
            console.log("Error while updating: %s", error);
        });
};

您可以注意到,现在我的所有服务方法都返回了一些内容,而在您最初的设计中它们是void类型。

最后,您可以将读取程序更改为:

function read() {
    console.log("");
    console.log(" Enter your choice: ");

    stdin = process.stdin;
    stdin.setEncoding('utf8');
    stdin.on('data', choice);
};

function choice (data) {
    data = parseInt(data);

    console.log("You entered: " + data);
    console.log("You choose to do: " + options[data]["option"]);
    options[data]["execute"]().then(function(){
        console.log("Enter your choice: ")
    });
};

read();

注意,在这种情况下,options[data]["execute"] 的执行使用了一个 promise。promise 的成功执行保证了在其他任务完成之前不会显示“输入您的选择”。
嘿,输出如下:
Enter your choice: 
1
You entered: 1
You choose to do: Save a User
User saved: [{"email":"john@example.com","password":"iLoveMongo","sex":"male","_id":"5388f57035a2eda13b7e4ea7"},{"n":0}]
Enter your choice: 
2
You entered: 2
You choose to do: Remove a User
User removed: {"n":2}

就是你想要的。


嗨,Edwin,谢谢你!没有 Promises 的适当解决方案会是什么样子? - mles
@mles 看起来就像我在前两段解释的那样。 - Edwin Dalorzo
好的,我已经使用回调函数修改了4个函数中的3个。那么saveuser()怎么办呢?我该如何修改它以拥有2个回调函数?我的目标是执行saveuser(),然后进行finduser()以查看插入的用户,最后再使用另一个回调函数,即function(){console.log("Enter your choice: ")} - mles
我认为我理解了你的意思,编辑名为 save 的函数,在回调中传回保存的文档,例如 callback(null, saved)。这样你就不需要绑定两个回调参数,就像你所暗示的那样。 - Edwin Dalorzo

0
function choice (data) {
    data = parseInt(data);

    console.log("You entered: " + data);
    console.log("You choose to do: " + options[data]["option"]);
    options[data]["execute"]();

     process.nextTick(function(){
       console.log("");
       console.log(" Enter your choice: ");
     });    
};

我正在使用Node.js异步地进行此操作,因此在options[data]["execute"]()完成之前,“输入您的选择”会弹出。我正在寻找一种方法,在options[data]["execute"]()完成再次输出“输入您的选择”。 - mles
@mles 然后你需要在你的函数中添加回调/承诺/事件,以便知道它们何时完成。 - Ben Fortune
我做了一些改动。使用process.nextTick的最简单方法 - Nikolay Lukyanchuk
也尝试过将回调函数添加到read()choice(),但结果并非我想要的。有时没有输出,有时会因为无限循环/调用栈溢出而崩溃。 - mles
@NikolayLukyanchuk 使用 nextTick 会得到相同的输出。 - mles
你需要为你的saveuser、removeuser、finduser、updateuser函数添加回调函数。你没有展示它们的代码,因此很难创建一些示例来帮助你。 - Nikolay Lukyanchuk

0
你可以考虑使用内置的readline模块来帮助你解决这些问题:
var readline = require('readline');

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

function choiceHandler(choice) {
  choice = parseInt(choice, 10);
  if (isNaN(choice))
    console.log('Bad choice, try again');
  else {
    console.log('You entered: ' + choice);
    console.log('You chose to do: ' + options[choice]['option']);
  }
  rl.prompt();
}

console.log('');
rl.setPrompt(' Enter your choice: ');
rl.on('line', choiceHandler);
rl.prompt();

我应该在哪里调用 options[data]["execute"]()?如果我将其添加到 else 分支中,输入您的选择 仍会在执行 options[data]["execute"]() 之前显示。 - mles
如果 options[data]["execute"]() 不能保证同步执行,那么可以使用 return options[data]["execute"](); 替代,并在需要显示提示时手动调用 rl.prompt(); - mscdex

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接