Javascript 循环中的承诺

Javascript 循环中的承诺,javascript,node.js,promise,Javascript,Node.js,Promise,在下面的代码中,我有一个无限循环,我不知道它为什么会发生。我最好的猜测是因为里面的函数是async循环不会等待它,因此循环永远不会停止。解决这个问题的最好办法是什么 var generateToken = function(userId) { return new Promise(function(resolve, reject) { User.findOne({userId: userId}, function(err, user) { if

在下面的代码中,我有一个无限循环,我不知道它为什么会发生。我最好的猜测是因为里面的函数是
async
循环不会等待它,因此循环永远不会停止。解决这个问题的最好办法是什么

 var generateToken = function(userId) {
    return new Promise(function(resolve, reject) {
        User.findOne({userId: userId}, function(err, user) {
            if (user !== null) {
                var loop = true;
                while (loop) {
                    var token = Common.randomGenerator(20);
                    (function(e) {
                        User.find({tokens: e}, function(err, result) {
                            if (err) {
                                loop = false;
                                reject('Error querying the database');
                            } else {
                                if (result.length === 0) {
                                    if (user.tokens === undefined) {
                                        user.tokens = [];
                                    }
                                    user.tokens.push(e);
                                    loop = false;
                                    resolve();
                                }
                            }
                        });
                    })(token);
                }
            } else {
                return reject('UserNotFound');
            }
        });
    });
};
此函数接收用户id(
User.findOne()
用于查找用户,如果没有具有该id的用户,则拒绝承诺),并为该用户创建一个唯一的随机令牌(
randomGenerator
),将其添加到MongoDB中保存的用户实体中,然后将其返回给调用方


注意有一些反对票说这个问题与我的代码中已经有一个闭包的问题相同,但它仍然不起作用。这个问题更多的是关于如何将循环变量绑定到闭包)

你是对的,你不能像你试图做的那样循环

Javascript是单线程的。因此,只要主线程在
while(loop)
语句中循环,其他任何线程都没有机会运行。如果主线程本身正在更改
循环
变量,那么这一切都是可以的,但事实并非如此。相反,您试图在异步响应中更改
循环
变量,但由于您的主线程正在循环,异步响应永远无法处理,因此您的
循环
变量永远无法更改,从而成为非生产性无限循环

若要修复,您必须更改为不同的循环构造。一种常见的设计模式是创建一个本地函数,其中包含要重复的代码。然后,运行异步操作,如果在异步结果处理程序中决定要重复该操作,则只需从其中再次调用本地函数。因为结果是异步的,所以堆栈已展开,从技术上讲,这不是堆栈构建的递归。它只是启动函数的另一个迭代

我对代码中的逻辑有点困惑(没有任何注释可以解释),所以我不完全确定我是否正确,但这里是总体思路:

var generateToken = function(userId) {
    return new Promise(function(resolve, reject) {
        User.findOne({userId: userId}, function(err, user) {
            function find() {
                var token = Common.randomGenerator(20);
                User.find({tokens: e}, function(err, result) {
                    if (err) {
                        reject('Error querying the database');
                    } else {
                        if (result.length === 0) {
                            if (user.tokens === undefined) {
                                user.tokens = [];
                            }
                            user.tokens.push(e);
                            resolve();
                        } else {
                            // do another find until we don't find a token
                            find();
                        }
                    }
                });
            }

            if (user !== null) {
                find();
            } else {
                reject('UserNotFound');
            }
        });
    });
};
我应该注意到,
User.findOne()
操作的错误处理不完整


仅供参考,持续查询直到得到
result.length==0
的整个逻辑似乎很奇怪。这种逻辑看起来很奇怪,而且闻起来像是“在一个紧密的循环中轮询数据库”,这通常是一件非常糟糕的事情。如果我们从更高的层次理解整个问题,我怀疑有更有效的方法来解决这个问题。

至于学习如何解决这类问题,您可能想看看异步库()。它提供了非常直观的方式来处理这样的异步情况,大多数熟悉同步迭代和javascript基础知识的人都能理解这种方式,对于几乎任何你能想象的异步迭代风格,并且被广泛使用并有很好的文档记录。

如果内部查找结果长度不是0,那么什么会将循环设置为false?如果我们确切知道这是为了什么,感觉所有这些都可以浓缩为一个查询。mongo对于属性和子文档都有一个非常强大的过滤系统。@KevinB的可能重复实际上这段代码是为了学习(不是一个真正的项目,我现在关心它的性能。即使我可以用其他方式来做,问题仍然是一样的)它包括两个查询,我不确定是否可以在Mongo中的一个查询中完成,但看看它是如何工作的可能会很有趣。@doldt:不,我在文章的最后一行添加了一条注释。
因为结果是异步的,堆栈已经展开,从技术上讲,这不是堆栈构建的递归。这只是启动该函数的另一个迭代。
你能详细说明一下这个想法吗?@ArianHosseinzadeh-包含的函数执行完毕,堆栈完全展开。然后异步操作完成,并使用干净的堆栈调用回调。@ArianHosseinzadeh-这是否为您解答了问题?一旦我找到时间,我将尝试并让您回答know@ArianHosseinzadeh-否。
find()
没有返回值,因此返回其返回值没有任何用处。它调用
resolve()
reject()
或启动另一个
find()
。这是它结束的三种方式。