【问题标题】:Async recursive calls driving me nuts异步递归调用让我抓狂
【发布时间】:2016-04-28 07:22:09
【问题描述】:

我有一个等级制度。一个类别有一个 parentid(如果它是根类别,则为 0)。

类别数据库中的示例行:

   name    |   id | parentid 
-----------+------+----------
 Label     |   71 |        8

我需要获取给定类别的所有子类别。 all 不仅是指一个类别的直接子代,而且是所有子代的每个子代。

我以前做过递归的东西,但在同步环境中,它的工作方式并不完全相同。

我知道我离一个可行的解决方案不远了,但它并不适用于所有情况。注意:删除了所有调试日志行,以免问题混乱。

此外,欢迎进行任何简化和/或优化。例如,我不喜欢有两个回调,一个是递归的,一个是最后一个......(但可能由于异步,它需要这样?)。

整个事情应该返回给定类别的所有子类别的 id 数组 (allCats)。

当前的解决方案适用于没有子级的单个类别,并且层次结构向下一层(并且allCats 正确包含所有 ID)。在两个级别上它都失败了(最终的回调永远不会被调用,所以cnt 没有被正确更新?)。

调用Category.getAllSubCategories(categoryId);开始搜索

  Category.getSubCategories = function(cat, cnt, fullCats, cb, finalCb) {        
    Category.find({where: {parentId: cat.id}}, function(err, cats) {
      if (err) {
        cb(err);
      }   
      if (cats.length > 0) {
        var ids = []; 
        for (var i=0; i<cats.length; i++) {
          ids.push(cats[i].id);
        }
        fullCats = fullCats.concat(ids);
        cb(null, cnt, fullCats, cats, finalCb);
      } else {
        if (cnt > 0) cnt -= 1;
        cb(null, cnt, fullCats, null, finalCb);
      }
    });

  }

  var catSearchCallback = function(err, cnt, fullCats, cats, finalCb) {
    if (err) {
      finalCb(err);
    }
    if (cats) {
      for (var c=0; c<cats.length; c++) {
        cnt += 1;
        Category.getSubCategories(cats[c], cnt, fullCats, catSearchCallback, finalCb);
      }
    } else {
      if (cnt == 0) {
        finalCb(null, fullCats);
      }
    }
  }

  /* start here */
  Category.getAllSubCategories = function(categoryId, cb) {
    Category.findById(categoryId, function(err, cat) {
      if (err) {
        return logger.error(err);
      }
      var fullCats = []; //collection holding ALL ids
      var cnt = 0;  //counter to count how many steps we have done
      if (cat) {
        fullCats.push(categoryId); //the category in question needs to be in the results as well
        Category.getSubCategories(cat, cnt, fullCats, catSearchCallback, function(err, allCats) {
          if (err) {
            cb(err);
          }
          cb(null, allCats);
        });
      } else {
        return categoryId;
      }
    });
  }

【问题讨论】:

  • 你尝试过使用承诺吗?我简化了所有这些回调的连接......
  • 我对 Promises 的掌握太少,无法自己提出一个可行的解决方案......我正在学习它们,但还没有
  • @faboolous 尝试从这里开始:bluebirdjs.com/docs/why-promises.html Promises 将解决这个问题。

标签: javascript asynchronous recursion loopbackjs


【解决方案1】:

我没有可使用的数据库,也没有完整的代码,例如我不知道Category.find 是如何工作的。但正如人们在 cmets 中所说,这是一个绝对可以使用 Promise 的代码,而我们在使用它时,为什么不使用 async/await。我自己无法运行此代码,因此将其视为蓝图/算法而不是完整代码。所以,这里什么都没有。

使用 async/await 重写代码:

Category.findAsync = function(condition){
    return new Promise((resolve, reject) => Category.find(
        condition,
        (err, result) => err ? reject(err) : resolve(result)
    ))
}

Category.getSubCategories = async function(parentId){
    return [parentId].concat(Promise.all(
        (await Category.findAsync({where: {parentId}})).map(
            subCategory => Category.getSubCategories(subCategory.id)
        )
    ))
}

运行它:

(async()=>{
    const categoryId = 12345
    console.log(await Category.getSubCategories(categoryId))
})()

因为我不知道find 是如何工作的,所以我编写了一个名为findAsync 的包装器,它将回调转换为promise。

* 回答结束 *

PS:召集所有 await/Promise 专家编辑此答案并帮助 OP 找到可行的解决方案。

【讨论】:

    【解决方案2】:

    以下似乎可以正常工作,我在系统中的零、一和两级层次结构上对其进行了测试,它达到了预期的效果(到目前为止......)。

    当然可能有更优雅的解决方案、更有效的解决方案等。 如果你有的话,非常欢迎分享。 对我来说,暂时,这是可行的:)

      /** 
        * Recursive iteration functions for getting subcategories 
        *
        * Starts with getAllSubCategories, and recursively call
        * - getSubCategories
        * - which in turn calls catSearchCallback as callback
        * 
        * In order to avoid race conditions, we can't use a global variable...
        * ...thus we need to pass the cumulated results (fullCats) and the
        * running queue (queue) as parameters to all involved functions.
        * The final "done" callback is also passed around until it's needed
        */
      Category.getSubCategories = function(cat, queue, fullCats, cb, done) {
        //load all subcategories of this the provided category is parent
        Category.find({where: {parentId: cat.id}}, function(err, cats) {
          if (err) {
            logger.error(err);
            cb(err);
          }   
          if (cats.length > 0) {
            cb(null, queue, fullCats, cats, cat.id, done);
          } else {
            cb(null, queue, fullCats, null, cat.id, done);
          }   
        }); 
    
      }
      /** 
        * callback after every subCategory
        */
      var catSearchCallback = function(err, queue, fullCats, cats, catId, done) {
        if (err) {
          logger.error(err);
          done(err);
        } 
        //first remove the returned category ID from the queue (means it has been processed)
        var index = queue.indexOf(catId);
        if (index > -1) {
          queue.splice(index, 1); 
        } else {
          //this should NOT HAPPEN!!!!
          logger.warn("NO CAT FOUND FOR REMOVAL");
        }
        //now if there are subcategories in this category, go further down
        if (cats) {
          for (var c=0; c<cats.length; c++) {
            //add this ID to the queue
            queue.push(cats[c].id);
            //add this ID to the final results
            fullCats.push(cats[c].id);
            //iterate this category
            Category.getSubCategories(cats[c], queue, fullCats, catSearchCallback, done);
          }
        } else {
          //there are no further subcategories for this category
          //and if the queue is empty, we are done
          if (queue.length == 0) {
            done(null, fullCats);
          }
        }
      }
      /**
        * start here for getting sub categories, provide categoryId from which to start 
        */
      Category.getAllSubCategories = function(categoryId, cb) {
        Category.findById(categoryId, function(err, cat) {
          if (err) {
            return cb(err);
          }
          var fullCats  = []; //this variable holds all collected IDs of categories which are subcategories of the given category
          var queue     = []; //this array stores the IDs which have been queried; when a category is queried by parent, its ID is added; 
                              //after returning from the callback, its ID is removed from here in order to clean the queue;
                              //we know that when the queue has become empty that all async calls have terminated
          if (cat) {
            //the category ID for which we are getting all subcategories needs to be included in final results!
            fullCats.push(categoryId);
            //add the first to the queue
            queue.push(categoryId);
            //now begin the recursive chain...
            Category.getSubCategories(cat, queue, fullCats, catSearchCallback, function(err, allCats) {
              if (err) {
                cb(err);
              }
              //...and when finished, terminate the complete call with the callback
              cb(null, allCats);
            });
          } else {
            logger.info("Category.getAllSubCategories: category not found");
            return categoryId;
          }
        });
      }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-11-19
      • 1970-01-01
      • 1970-01-01
      • 2010-12-29
      • 2017-10-28
      • 2018-01-30
      • 2011-05-09
      • 2014-05-19
      相关资源
      最近更新 更多