【问题标题】:How to get around this MongoDB/Node asynchronous issue?如何解决这个 MongoDB/Node 异步问题?
【发布时间】:2013-02-09 17:26:11
【问题描述】:

我有以下代码:

// 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 条帐户已经存在的消息。

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

【问题讨论】:

  • 所以你想让第二、第三和第四次插入失败?
  • 是的,因为该帐户应该已经存在(但还不存在)。
  • 最佳实践是在email 上添加一个唯一索引,然后处理insert 错误,如果存在另一种形式的"Account already exists." 错误。
  • 这听起来是个不错的处理方式。
  • 你也可以尝试用 Promise 解决问题,参见:stackoverflow.com/questions/11912573/…

标签: javascript node.js mongodb asynchronous


【解决方案1】:

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

JavaScript 没有,您必须将 createAccount 调用与回调链接起来。

有些人开发了可以帮助您组织此代码的库。例如asyncstepnode-promiseQ

您还可以使用 fibers 库,这是一个使用纤程/协程扩展 JavaScript 运行时的本机库。

有些人用类似于async/await 的结构扩展了语言:streamline.jsIcedCoffeeScriptwind.js。例如,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.")
    }
}

最后但并非最不重要的一点是,generatorsdeferred functions 等新的语言特性正在讨论用于 JavaScript 的未来版本(生成器很可能登陆 ES6,延迟函数似乎有点停滞不前)。

所以你有很多选择:

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

【讨论】:

  • 另一个值得一提的选项是tamejs,它与 IcedCoffeeScript 的开发者相同——实际上,它是在纯 JS 中工作的原始版本。但由于某种原因,它生成的代码量是 streamline.js 的两倍(尽管函数数量大致相同),所以我推荐 streamline.js。此外,streamline.js 可以让您使用 try/catch 更自然地处理错误,并且有一个使速度更快的纤维选项。
  • 另外值得注意的是,streamline.js 也可以与 CoffeeScript 一起使用(通过在 CoffeeScript 转换后应用 Streamline.js 转换;文档中的更多详细信息)。
【解决方案2】:

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

【讨论】:

    【解决方案3】:

    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();
                }
    
            }
        });
    }
    

    【讨论】:

    • 我明白为什么会这样,我想知道解决它的最佳方法是什么。
    • 我添加了上面的代码,以展示在 JavaScript 中执行此操作的正确方法。或者,您可以使用 Step 库github.com/creationix/step
    • 我倾向于根据更新对这个答案投反对票:“这是在 Javascript 中执行此操作的正确方法”。首先是关于 Node.js 中的异步使用,而不是 Javascript,很少有一种“正确”的方式可以在代码中做任何事情。希望任何理性的开发人员都能认识到这种情况下的嵌套回调不会扩展,也不是理想的解决方案。我同意这句话:“但是,多于几级的嵌套应该是一种代码味道——是时候考虑一​​下你可以将什么抽象成单独的小模块了。”通过book.mixu.net/node/ch7.html
    猜你喜欢
    • 1970-01-01
    • 2021-03-27
    • 2010-12-22
    • 2020-02-22
    • 1970-01-01
    • 1970-01-01
    • 2020-06-02
    • 1970-01-01
    • 2014-10-07
    相关资源
    最近更新 更多