【问题标题】:Recursion with promises and concurrency具有承诺和并发性的递归
【发布时间】:2021-08-08 12:56:57
【问题描述】:

请耐心等待,因为这需要一些出发点。

出于问题/讨论的目的,考虑一下这个平凡的价值树。

const valuetree = [{
    item: 1,
    value: 100,
  },
  {
    item: 2,
    value: [{
      item: 3,
      value: [{
          item: 4,
          value: 200
        },
        {
          item: 5,
          value: 200
        },
        {
          item: 6,
          value: 200
        },
      ]
    }]
  },
];

现在,如果我们想编写一个递归函数来将所有值相加,那么这本身就相当简单。像这样:

function sumTree(leaf) {
  let total = 0;

  if (Array.isArray(leaf)) {
    leaf.forEach(subLeaf => {
      total += sumTree(subLeaf);
    });
  } else {
    if (Array.isArray(leaf.value)) {
      total += sumTree(leaf.value);
    } else {
      total += Number(leaf.value);
    }
  }

  return total;
}

现在假设我们想使用 Promise 来实现相同的目标?

这就是我最终的结果......

async function sumTreeAsync(leaf) {
  let promises = [];

  if (Array.isArray(leaf)) {
    leaf.forEach(async subLeaf => {
      promises.push(sumTreeAsync(subLeaf));
    });
  } else {
    if (Array.isArray(leaf.value)) {
      promises.push(sumTreeAsync(leaf.value));
    } else {
      promises.push(leaf.value);
    }
  }

  return promises;
}

所以返回的内容是这样的

[ Promise { [ 100 ] }, Promise { [ [Promise] ] } ]

这是有道理的,一个由 2 个顶级项组成的数组,一个字面值的承诺,以及一个返回嵌套的承诺数组的承诺。

所以现在像 Promise.all 这样的函数只处理一组平面的 Promise,我必须递归嵌套的 Promise 才能得到相同的结果。所以现在我必须递归这棵树两次,这闻起来很糟糕。

所以也许我需要从子叶是数组的 2 种情况中返回已解决的承诺?

这样做是否值得,或者递归应该是同步的?

【问题讨论】:

  • 你期望什么作为返回值?你的目标是什么?你可能想要一个迭代器?不知道你为什么要承诺。
  • 一个函数通常不应该返回一个promise数组,而只是调用Promise.all本身并返回一个数组的promise。
  • 我想要做的是以最省时的方式遍历树并将所有值加起来为一个总量。因为它显然可以在每个级别同时处理,所以我想使用 async/await。但是当你将递归和承诺混为一谈时,事情就变得有点复杂了。
  • @ScottSauyet 是的,这意味着一般性的批评,以及指向有关异步循环的规范问题的指针。当然,具体来说,如果只是将 promise 推送到数组中,这个函数首先不应该是async,并且应该简化为return Promise.all(leaf.map(sumTreeAsync))。但是 trincot 的问题仍然存在,在我们得到 OP 的回复之前,我不会写答案。

标签: javascript recursion promise tree es6-promise


【解决方案1】:

离你不远了。如果你改变你的回报来做一个Promise .all调用和.then (sum)一个明显的sum函数,这会起作用:

const sum = (ns) => ns .reduce ((a, b) => a + b, 0)

async function sumTreeAsync(leaf) {
  let promises = [];

  if (Array.isArray(leaf)) {
    leaf.forEach(async subLeaf => {
      promises.push(sumTreeAsync(subLeaf));
    });
  } else {
    if (Array.isArray(leaf.value)) {
      promises.push(sumTreeAsync(leaf.value));
    } else {
      promises.push(leaf.value);
    }
  }

  return Promise.all(promises).then(sum);
}

const valuetree = [{item: 1, value: 100}, {item: 2, value: [{item: 3, value: [{item: 4, value: 200}, {item: 5, value: 200}, {item: 6, value: 200}]}]}];

sumTreeAsync (valuetree)
  .then (console .log)

但我肯定会以不同的方式编写这段代码:

const sum = (ns) => ns .reduce ((a, b) => a + b, 0)

const sumTreeAsync = async (leaf) =>
  Promise .all (Array .isArray (leaf)
    ? leaf.map (sumTreeAsync)
    : Array .isArray (leaf .value)
      ? [sumTreeAsync (leaf .value)]
      : [leaf .value]
  ) .then (sum)

const valuetree = [{item: 1, value: 100}, {item: 2, value: [{item: 3, value: [{item: 4, value: 200}, {item: 5, value: 200}, {item: 6, value: 200}]}]}];

sumTreeAsync (valuetree)
  .then (console .log)

不止于此。我可能根本不会写这段代码。 JS 一般还是单线程语言。所以在这种方法中你根本不会得到并发计算。如果您不是简单的总结,而是将事情交给不同的工作人员处理,那么这可能是有道理的。事实上,这只是一种不必要的复杂化。

【讨论】:

  • 谢谢斯科特。正如我所说,这是一个微不足道的例子,问题的关键在于并发性。我同意,这看起来像一个不必要的并发症。我知道 js 是单线程的,但我真的想知道通过使用 async/await 使这个并发处理是否有好处。如果不是对值求和,每个值是一段要执行的代码,而不是一棵树,而是一个具有并行路径的图......
  • 很难想象会从中获得任何处理收益,只有损失。但是对于更复杂的例子,并且在工人面前,我们必须再看一遍。
  • @CharlieBenger-Stevenson “通过使用 async/await 使其并发。”不会使其并发。 Promise 仍然是按顺序解决的。为了获得并发,您需要将处理交给工作人员或其他进程。示例代码绝对没有理由异步,因为值在那里。但是,如果树是异步填充的,或者值是异步的(例如,树是预先构建的,但您为每个叶值触发 fetch()),则它可能有意义。或者您无法同时获得所有数据的任何情况。
猜你喜欢
  • 2014-01-03
  • 2017-04-08
  • 2014-02-04
  • 1970-01-01
  • 2017-05-05
  • 2011-08-21
  • 2018-05-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多