Node.js 在nodejs中同步递归调用函数

Node.js 在nodejs中同步递归调用函数,node.js,express,recursion,mongoose,Node.js,Express,Recursion,Mongoose,我正在开发MEANJS项目。这里是一个函数,通过递归获取数据返回数组。现在,我的问题是如何获得该数组 问题:- 让用户A拥有的属性是{u-id:'001',rmUserId:'001',B:{u-id:'002',rmUserId:'001',C{u-id:'003',rmUserId:'002',D{u-id:'003',rmUserId:'001',E{u-id:'004',rmUserId:'009} 若用户A将登录,则allUnderUsers数组中有B、C、D用户。这意味着所有用户都必

我正在开发MEANJS项目。这里是一个函数,通过递归获取数据返回数组。现在,我的问题是如何获得该数组

问题:-

让用户A拥有的属性是{u-id:'001',rmUserId:'001',B:{u-id:'002',rmUserId:'001',C{u-id:'003',rmUserId:'002',D{u-id:'003',rmUserId:'001',E{u-id:'004',rmUserId:'009}

若用户A将登录,则allUnderUsers数组中有B、C、D用户。这意味着所有用户都必须遵循自己的层次结构

   A
  / \
 B   D
/
C
这是我的密码:-

module.exports.getUnderUsersByRm = function(currentUser, callback) {
    try {
        var allUnderUsers = [];
        function __getUserByRmId(rmId) {
            User.find({ rmUserId: rmId, isAlive: true, status: 'active' })
                .exec(function(err, users) {
                    if (err)
                        return callback(err)
                    if (users.length > 0) {
                        users.forEach(function(ele, i) {
                            allUnderUsers.push(ele);
                            __getUserByRmId(ele.rmUserId);
                        });
                    } else {
                        return false;
                    }
                })
        }
        __getUserByRmId(currentUser._id);
    } catch (e) {
        callback(e)
    }
}
这里我需要在调用所有递归函数之后获得allUnderUsers数组

我使用了如下回调函数:-

....
...
__getUserByRmId(currentUser._id);
callback(null,'done');
.
.
但它抛出了一个错误,即

错误:发送邮件后无法设置邮件头

.at ServerResponse.OutgoingMessage.setHeader\u http\u outgoing.js:346:11 在ServerResponse.header/home/clsah/projects/LMS/node_modules/express/lib/response.js:719:10 ........ .......

使用并维护要处理的元素数组
我很确定您会遇到这个错误,因为您运行了uu getUserByRmId函数,如果发生错误,该函数在内部调用回调函数,然后,除了发生的任何事情外,您还会再次调用回调函数,该函数可能会对同一个请求发送多个响应,等等,即使已发送响应,也要多次设置标题


我应该在评论中发布这一点,但没有足够的声誉这样做

如果您利用mongoose更高版本中内置的承诺,并从模块中呈现承诺界面,您可以这样做:

此处模拟运行代码:

然后你会这样使用它:

const someModule = require('yourModule');

someModule.getUnderUsersByRm(someUser).then(results => {
    // process all results here
}).catch(err => {
    // error here
});
如果您仍然希望在getUnderUsersByRm上使用回调接口,您仍然可以这样做。不过,如果您要执行的异步调用多于几个,那么对所有异步操作使用承诺确实是值得的:

module.exports.getUnderUsersByRm = function(currentUser, callback) {
    function __getUserByRmId(rmId) {
        // return promise here
        return User.find({ rmUserId: rmId, isAlive: true, status: 'active' }).exec().then(function(users) {
            if (users.length > 0) {
                let promises = [];
                users.forEach(function(ele, i) {
                    promises.push(__getUserByRmId(ele.rmUserId));
                });
                // return promise which will chain it to original promise
                // this is the key to getting the master promise to wait
                // for everything to be done
                return Promise.all(promises).then(results => {
                    // add in previous results
                    // flatten all the results together into a single array
                    // and remove empty results
                    results.unshift(users);
                    return [].concat.apply([], results.filter(item => item.length > 0));
                });
            } else {
                return [];
            }
        });
    }
    __getUserByRmId(currentUser).then(result => {
        callback(null, result);
    }).catch(err => {
        callback(err);
    });
}
如果您的用户树是循环的,那么您可以通过跟踪所有访问的用户来防止无限循环。您需要某种标识每个用户的唯一密钥。由于我不知道您的程序中有什么,我将假定您要传递的用户已经是一个id。唯一标识该用户的任何属性都将在此方案中工作:

module.exports.getUnderUsersByRm = function(currentUser) {
    let visitedUsers = new Set();

    function __getUserByRmId(rmId) {
        // return promise here
        return User.find({ rmUserId: rmId, isAlive: true, status: 'active' }).exec().then(function(users) {
            if (users.length > 0) {
                let promises = [];
                users.forEach(function(ele, i) {
                    // make sure we aren't already processing this user
                    // avoid circular loop
                    let userId = ele.rmUserId;
                    if (!visitedUsers.has(userId)) {
                        visitedUsers.add(userId);
                        promises.push(__getUserByRmId(userId));
                    }
                });
                // return promise which will chain it to original promise
                // this is the key to getting the master promise to wait
                // for everything to be done
                return Promise.all(promises).then(results => {
                    // add in previous results
                    // flatten all the results together into a single array
                    // and remove empty results
                    results.unshift(users);
                    return [].concat.apply([], results.filter(item => item.length > 0));
                });
            } else {
                return [];
            }
        });
    }
    return __getUserByRmId(currentUser);
}

我尝试使用基于异步的方法实现该解决方案。你可能会觉得这很幼稚,但它应该会起作用

function __getUserByRmId(rmId, cb) {
  var allUnderUsers = [];
  User.find({ rmUserId: rmId, isAlive: true, status: 'active' })
      .exec(function(err, users) {
        async.each(users, function(user, callback){
          if (user._id != rmId){
            // recursive call 
            __getUserByRmId(user._id, function(childUsers){
              childUsers.forEach(function (childUser) {
                allUnderUsers.push(childUser);
              });
              callback(); //intermediate callback for async call  
            });
          } else { //condition check to avoid infinite loop
            allUnderUsers.push(user);
            callback(); //intermediate callback for-loop 
          }

        }, function(err){
            cb(allUnderUsers);  //final callback with result
        });
      });
}


module.exports.getUnderUsersByRm = function(currentUser, callback) {
  __getUserByRmId(currentUser._id, callback)
};

从逻辑上讲,它应该是有效的。如果有任何问题,请尝试并让我知道。目前,它还返回包含父级的数组。e、 g.[A,B,C,D]作为示例。

return-rejecterr在代码中的作用是什么?代码中任何地方都没有定义拒绝函数。看起来您试图在没有正确执行回调和承诺的情况下混合使用回调和承诺。很抱歉拼写错误。我已经编辑过了。请现在检查。基本上,我需要在调用所有函数后使用回调函数。您需要知道递归何时完成,并执行callbacknull,allUnderUsers。看起来uuu getUserBYRmId需要自己的回调来告诉您何时完成,如果实现正确,传递给该函数顶级调用的回调将告诉您何时完成递归。感谢@jfriend00的回复,但我不清楚。为了更清楚,我再次编辑了这个问题。请检查。您使用的猫鼬版本是什么?如果我们可以使用最新版本中内置的promise支持,这将容易得多。是的,我需要类似这样的东西。但是,它从不调用最终回调,而是在catch上面的行中调用。如果它没有调用,你能在一些日志中检查usersToProcess.length在uu getUserByRmId函数的顶部吗?它将在无限调用中调用自己。当我在u getUserByRmId函数的顶部放置console.logusersToProcess时,正如你所说,它会不断地调用自己,无限循环中的陷阱。它应该调用自己,直到你们的层次结构被覆盖,但不是无限的。不过可能有一些bug。在每次迭代中,应通过.shift从数组中删除1个元素,并使用.push追加一些元素。async.until应该在数组长度为0时退出。它在catch块中也没有调用最终回调,在无限循环中被捕获。@vineetsah-您认为在哪里有无限循环?我不明白你的评论。我假设您的用户树不是循环的,这将导致无限循环。即使这样,也可以通过保留一组访问过的用户名来修复,但是您的原始实现和图表没有显示这一点,因此我没有实现这种循环保护。@vineetsah-我的第一个代码块返回一个承诺。函数的调用方使用承诺来检索结果,如我的示例所示。它不传递或使用回调。我的第二个示例显示了旧式回调。对于所有异步开发,您真的应该转向承诺。一旦你学会了编程,编程就容易多了——特别是在有multip的情况下
您需要协调和传播错误的异步操作。@vineetsah-I添加了第三个选项,可以防止用户循环。我假设你的数据不是以循环的方式组织的,但是你对无限循环的坚持让我认为你的数据可能有这样的设计,所以我提供了一个解决方案。在第三个选项中,我们跟踪所有已经处理过的用户ID值,并且只跟踪尚未处理过的用户。@vineetsah-是的,代码中存在一些问题,无法返回累积结果。当您无法运行代码进行测试时,很难编写完美的代码。这是一个可运行的版本,带有更正:。我会纠正我的答案,像这样工作。
module.exports.getUnderUsersByRm = function(currentUser) {
    let visitedUsers = new Set();

    function __getUserByRmId(rmId) {
        // return promise here
        return User.find({ rmUserId: rmId, isAlive: true, status: 'active' }).exec().then(function(users) {
            if (users.length > 0) {
                let promises = [];
                users.forEach(function(ele, i) {
                    // make sure we aren't already processing this user
                    // avoid circular loop
                    let userId = ele.rmUserId;
                    if (!visitedUsers.has(userId)) {
                        visitedUsers.add(userId);
                        promises.push(__getUserByRmId(userId));
                    }
                });
                // return promise which will chain it to original promise
                // this is the key to getting the master promise to wait
                // for everything to be done
                return Promise.all(promises).then(results => {
                    // add in previous results
                    // flatten all the results together into a single array
                    // and remove empty results
                    results.unshift(users);
                    return [].concat.apply([], results.filter(item => item.length > 0));
                });
            } else {
                return [];
            }
        });
    }
    return __getUserByRmId(currentUser);
}
function __getUserByRmId(rmId, cb) {
  var allUnderUsers = [];
  User.find({ rmUserId: rmId, isAlive: true, status: 'active' })
      .exec(function(err, users) {
        async.each(users, function(user, callback){
          if (user._id != rmId){
            // recursive call 
            __getUserByRmId(user._id, function(childUsers){
              childUsers.forEach(function (childUser) {
                allUnderUsers.push(childUser);
              });
              callback(); //intermediate callback for async call  
            });
          } else { //condition check to avoid infinite loop
            allUnderUsers.push(user);
            callback(); //intermediate callback for-loop 
          }

        }, function(err){
            cb(allUnderUsers);  //final callback with result
        });
      });
}


module.exports.getUnderUsersByRm = function(currentUser, callback) {
  __getUserByRmId(currentUser._id, callback)
};