【问题标题】:Call synchronously recursively function in nodejs在nodejs中同步递归调用函数
【发布时间】:2017-06-08 20:02:10
【问题描述】:

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

问题:-

让用户A拥有属性为{_id:'001',rmUserId:'001'},B:{_id:'002',rmUserId:'001 '}, C {_id:'003',rmUserId:'002'}, D {_id:'003',rmUserId:'001'}, E {_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 (_http_outgoing.js:346:11) 在 ServerResponse.header (/home/clsah/projects/LMS/node_modules/express/lib/response.js:719:10) ........ ........

【问题讨论】:

  • return reject(err) 在你的代码中做了什么?在代码中的任何地方都没有定义 reject() 函数。看起来您正在尝试混合回调和承诺,而实际上没有正确执行其中任何一个。
  • 抱歉拼写错误。我已经编辑了它们。请现在检查。基本上,我需要在调用所有函数后使用callback函数。
  • 您需要以某种方式知道递归何时完成并执行callback(null, allUnderUsers)。看起来__getUserBYRmId() 需要它自己的回调来告诉您何时完成,如果实施得当,传递给该函数的顶级调用的回调将告诉您递归何时完成。
  • 感谢@jfriend00 的回复,但我不清楚。为了更清楚,我现在再次编辑了问题。请检查。
  • 你用的是什么版本的猫鼬?如果我们可以使用最新版本中内置的 Promise 支持,这将容易得多。

标签: node.js express recursion mongoose


【解决方案1】:

如果您利用 mongoose 更高版本中内置的 Promise 并从您的模块中显示一个 Promise 接口,您可以这样做:

此处模拟运行代码:https://jsfiddle.net/jfriend00/zr6ynmsu/

module.exports.getUnderUsersByRm = function(currentUser) {
    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 [];
            }
        });
    }
    return __getUserByRmId(currentUser);
}

然后,你会像这样使用它:

const someModule = require('yourModule');

someModule.getUnderUsersByRm(someUser).then(results => {
    // process all results here
}).catch(err => {
    // error here
});

如果您仍然希望 getUnderUsersByRm 上的回调接口,您仍然可以这样做(尽管如果您执行的异步调用不止几个,那么对所有异步操作都使用 Promise 确实值得):

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

【讨论】:

  • 在catch块中也没有调用final回调,陷入无限循环。
  • @vineetsah - 你认为哪里有无限循环?我不明白你的评论。我假设您的用户树不是循环的(这会导致无限循环)。即使这可以通过保留一组访问过的用户名来解决,但是您的原始实现和图表没有显示,所以我没有实现那种类型的循环保护。
  • @vineetsah - 我的第一个代码块返回一个承诺。函数的调用者使用 promise 来检索结果(如我的示例所示)。它不传递或使用回调。我的第二个示例显示了旧式回调。你真的应该切换到所有异步开发的承诺。一旦你学会了编程,它就会变得非常容易——尤其是在这样的情况下,当你需要协调多个异步操作并需要传播错误时。
  • @vineetsah - 我添加了第三个选项来防止用户循环。我假设您的数据不是以循环方式组织的,但是您对无限循环的坚持使我认为您的数据可能具有这种设计,因此我为此提供了解决方案。在第三个选项中,我们跟踪我们已经处理的所有 userId 值,并且只跟踪我们尚未处理的用户。
  • @vineetsah - 是的,代码中存在一些问题,它没有返回累积结果(当您无法运行它进行测试时,很难编写完美的代码)。这是带有更正的可运行版本:jsfiddle.net/jfriend00/zr6ynmsu。我会更正我的答案,像这样工作。
【解决方案2】:

使用async.until 并维护要处理的元素数组

var async = require('async');

module.exports.getUnderUsersByRm = function(currentUser, callback) {
  try {
    var allUnderUsers = [];
    var usersToProcess = [currentUser._id]; // Array to track what was earlier done with recursion

    async.until(function() { // Test function for async.until
      return usersToProcess.length === 0;
    }, function __getUserByRmId(callback2) { // fn for async.until
        User.find({
            rmUserId: usersToProcess.shift(), // Take first element of array
            isAlive: true,
            status: 'active'
          })
          .exec(function(err, users) {
            if (err)
              return callback2(err)
            if (users.length > 0) {
              users.forEach(function(ele, i) {
                allUnderUsers.push(ele);
                usersToProcess.push(ele.rmUserId);
                // __getUserByRmId(ele.rmUserId); // To Remove
              });
              return callback2(); // Always call callback;
            } else {
              return callback2(); // Earlier: return false; Return some err argument if you want
            }
          })
    }, callback); // Final callback for async.until
  } catch (e) {
    callback(e);
  }
}

【讨论】:

  • 是的,我需要这样的东西。但是,它从不调用最终回调。
  • 它在catch上方的行中被调用。如果它没有调用,你可以在__getUserByRmId函数顶部输入一些日志来检查usersToProcess.length
  • 它会无限调用自己。
  • 当我把console.log(usersToProcess) 放在__getUserByRmId 的顶部时,正如你所说,它会不断地调用它自己,陷入无限循环。
  • 它应该调用自己,直到你的层次结构被覆盖但不是无限的。虽然可能有一些错误。在每次迭代中,应使用 .shift() 从数组中删除 1 个元素,并应使用 .push() 添加一些元素。 async.until 应该在数组长度为 0 时退出。
【解决方案3】:

我尝试使用基于async 的方法来实施该解决方案。你可能会觉得它很幼稚,但它应该可以工作。

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

从逻辑上讲,它应该可以工作。请试一试,如果有任何问题,请告诉我。现在,它也返回包含父级的数组。例如[A, B, C, D] 为您的示例。

【讨论】:

    【解决方案4】:

    我很确定您会收到该错误,因为您运行 __getUserByRmId 函数(如果发生错误,该函数在内部调用 callback 函数,然后,除了发生的任何事情之外,您再次调用 callback 函数,这可能会向同一个请求发送多个响应,依此类推,即使响应已经发送,也会多次设置标头。

    我应该在 cmets 中发布此内容,但没有足够的声誉这样做

    【讨论】:

      猜你喜欢
      • 2017-06-27
      • 2022-12-31
      • 2016-09-21
      • 1970-01-01
      • 2014-05-21
      • 2019-02-20
      • 2017-05-21
      • 2017-03-12
      • 2020-02-10
      相关资源
      最近更新 更多