Javascript 如何绕过MongoDB/节点异步问题?

Javascript 如何绕过MongoDB/节点异步问题?,javascript,node.js,mongodb,asynchronous,Javascript,Node.js,Mongodb,Asynchronous,我有以下代码: // Retrieve var MongoClient = require("mongodb").MongoClient; var accounts = null; var characters = null; // Connect to the db MongoClient.connect("mongodb://localhost:27017/bq", function(err, db) { if(err) { return console.dir(err); }

我有以下代码:

// Retrieve
var MongoClient = require("mongodb").MongoClient;
var accounts = null;
var characters = null;

// Connect to the db
MongoClient.connect("mongodb://localhost:27017/bq", function(err, db) {
   if(err) { return console.dir(err); }

    db.createCollection('accounts', function(err, collection) {
        if(err) { return console.dir(err); }
        else { accounts = collection; }

        createAccount("bob","bob");
        createAccount("bob","bob");
        createAccount("bob","bob");
        createAccount("bob","bob");
    });
});


function createAccount(email, password)
{
    accounts.findOne({"email":email}, function(err, item) {
        if(err) { console.dir(err); }
        else {
            if(item === null) {
                accounts.insert({"email":email, "password":password}, function(err, result) {
                    if(err) { console.dir(err); }
                    else { console.dir("Account " + email + " created."); }
                });
            }
            else {
                console.dir("Account already exists.")
            }

        }
    });
}
当我第一次运行脚本时,bob有4个帐户。当我第二次运行它时,我收到4条消息,表明该帐户已经存在


我很确定我知道这是为什么,我提出的解决方案是使用某种队列来处理数据库的每次读/写,每次一个。我想知道的是,这是否是正确的方法,一般的最佳实践是什么?

JavaScript是异步的
accounts.findOne
立即返回,因此基本上所有4条语句都在一起执行

accounts.findOne
所做的是,它说查找一个
{“email”:email}
,当您找到它时,运行第二个参数中的函数。然后返回函数并继续下一个CreateAccount语句。同时,当结果从硬盘返回时(这比执行这些语句花费的时间要长得多),它会进入函数,因为没有用户,所以会添加一个。有道理吗

更新这是在JavaScript中执行此操作的正确方法

MongoClient.connect("mongodb://localhost:27017/bq", function(err, db) {
   if(err) { return console.dir(err); }

    db.createCollection('accounts', function(err, collection) {
        if(err) { return console.dir(err); }
        else { accounts = collection; }

        createAccount("bob","bob", function() {
            createAccount("bob","bob", function() {
                createAccount("bob","bob", function() {
                    createAccount("bob","bob", function() {
                     });
                });
            });
        });
    });
});


function createAccount(email, password, fn)
{
    accounts.findOne({"email":email}, function(err, item) {
        if(err) { console.dir(err); }
        else {
            if(item === null) {
                accounts.insert({"email":email, "password":password}, function(err, result) {
                    if(err) { console.dir(err); }
                    else { console.dir("Account " + email + " created."); }
                    fn();
                });
            }
            else {
                console.dir("Account already exists.")
                fn();
            }

        }
    });
}

在电子邮件上添加一个唯一的约束,您将不必再检查用户是否存在

一些语言提供了一种特殊的语言结构来处理这个问题。例如,C#有
async
/
await
关键字,让您编写代码时就像调用同步API一样

JavaScript没有,您必须用回调链接
createAccount
调用

有些人开发了一些库,可以帮助您组织这些代码。例如

您还可以使用该库,它是一个本机库,通过光纤/协同路由扩展JavaScript运行时

有些人用类似于
async
/
await
:,或。例如,streamline.js(我是作者,所以我显然有偏见)使用
\
作为一个特殊的回调占位符,并允许您将示例编写为:

var db = MongoClient.connect("mongodb://localhost:27017/bq", _):
var accounts = db.createCollection('accounts', _);
createAccount("bob","bob", _);
createAccount("bob","bob", _);
createAccount("bob","bob", _);
createAccount("bob","bob", _);

function createAccount(email, password, _) {
    var item = accounts.findOne({"email":email}, _);
    if (item === null) {
        accounts.insert({"email":email, "password":password}, _);
        console.log("Account " + email + " created."); }
    } else {
        console.log("Account already exists.")
    }
}
最后但并非最不重要的一点是,诸如和等新的语言特性正在为未来的JavaScript版本进行讨论(生成器很可能在ES6中实现,延迟函数似乎有点停滞)

所以你有很多选择:

  • 坚持回调
  • 使用辅助程序库
  • 使用光纤运行时扩展
  • 使用语言扩展名
  • 等待ES6

所以您希望第二次、第三次和第四次插入失败?是的,因为该帐户应该已经存在(但现在还没有)。最佳做法是在
电子邮件
上添加一个唯一索引,然后处理
插入
错误,如果该
帐户已经存在,则会出现重复
error。这听起来是一个很好的处理方法。您也可以尝试用承诺来解决问题,请参阅:我理解为什么会发生这种情况,我想知道的是最好的解决方法是什么。我添加了上面的代码,以展示用JavaScript做这件事的正确方法。或者,您可以使用步骤库,我倾向于否决这个基于更新的答案,该更新说:“这是在Javascript中这样做的正确方法”。首先,关于Node.js中的异步使用,而不是Javascript,很少有一种“正确”的方法在代码中执行任何操作。希望任何rational开发人员都能认识到,这种场景中的嵌套回调不会扩展,并且不是理想的解决方案。我同意这句话:“然而,多层次的嵌套应该是一种代码味道——是时候想想你可以把什么抽象成单独的、小的模块了。”通过另一个值得一提的选项是,它是由与IcedOfficeScript相同的开发人员开发的——实际上,它是在纯JS中工作的原始版本。但由于某些原因,它生成的代码是streamline.js的两倍(尽管函数数量大致相同),因此我建议使用streamline.js。此外,streamline.js可以让您通过try/catch更自然地处理错误,并且有一个fibers选项可以加快处理速度。另外值得注意的是,streamline.js也可以与CoffeeScript一起使用(通过在CoffeeScript转换后应用streamline.js转换;文档中有更多详细信息)。