异步递归调用让我发疯



我有一个分层系统。类别具有父 ID(如果是根类别,则为 0)。

类别数据库中的示例行:

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

我需要获取给定类别的所有子类别。所谓"一切",不仅指一个类别的直系子女,而且指所有子女的每个子女。

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

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

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

整个事情应该返回给定类别的所有子类别的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;
      }
    });
  }

以下内容似乎正在工作,我在系统中的零级、一级和两级层次结构上对其进行了测试,它完成了预期(到目前为止......

当然可能会有更优雅的解决方案,更有效的解决方案等。如果你有的话,非常欢迎你分享它。对我来说,就目前而言,这:)

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

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

使用 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 ,它将回调转换为承诺。

*

回答结束 *

PS:召集所有等待/承诺专家编辑此答案并帮助OP达成可行的解决方案。

最新更新