【问题标题】:Usage of Promise.All in recursion doesn't seems to be workingPromise.All 在递归中的使用似乎不起作用
【发布时间】:2020-05-04 09:59:11
【问题描述】:

实际的doSomething 函数将ele 发布到远程API 以进行一些计算。

我的 calc 函数应该获取远程 API 对每个元素的计算总和,它应该为每个元素运行而不影响它们的嵌套方式。

但是,目前,我无法使其正常工作。我该如何解决这个问题?

const doSomething = (ele) => new Promise(resolve => {
    console.log(ele);
    resolve(ele * 2);//for example
})

const calc = (arr) => new Promise(
    async(resolve) => {
        console.log(arr.filter(ele => !Array.isArray(ele)));
        let sum = 0;
        const out = await Promise.all(arr.filter(ele => !Array.isArray(ele))
            .map(ele => doSomething(ele)));
        sum += out.reduce((a, b) => a + b, 0);
        const out2 = await Promise.all(arr.filter(ele => Array.isArray(ele))
            .map(ele => calc(ele)));
        sum += out2.reduce((a, b) => a + b, 0);
        resolve(sum);

    }
)

const process = async () => {
    console.log('processing..');
    const arr = [1, 2, 3, 4, 5, [6,7], 1, [8,[10,11]]];
    const out = await calc(arr);
    console.log(out);
}


process();

【问题讨论】:

  • 您所说的“我无法让它工作”是什么意思?当前的行为是什么?预期的行为是什么?
  • 你使用 async/await 作为 Promise 构造函数的事实表明你不知道 Promise :p
  • dosomething !== doSomething - 另外,out2 是一个数组,所以 sum += out2 会搞砸你
  • 好吧,现在你在问题中修复了你的代码,我的回答解决了这些问题有点尴尬 - 看起来我没有阅读你的代码并通知你其中的错误
  • 没有弹出任何错误

标签: javascript recursion promise async-await


【解决方案1】:

虽然看起来我已经解决了不存在的问题 - 问题中的原始代码具有我在此答案中解决的所有缺陷,包括下面的第二个和第三个

是的,问题中的代码现在可以工作了!但它显然是有缺陷的

首先:在 calc 函数中不需要 Promise 构造函数,因为你使用 Promise.all 返回一个 Promise,如果你使 calc 异步,只需使用 await

第二个:dosomething !== doSomething

第三:out2 是一个数组,所以sum += out2 会搞砸你

第四:.map(ele => doSomething(ele)) 可以写成.map(doSoemthing) - 和calc(ele) 映射一样

所以,工作代码变成:

const doSomething = (ele) => new Promise(resolve => {
  resolve(ele * 2); //for example
})

const calc = async(arr) => {
  const out = await Promise.all(arr.filter(ele => !Array.isArray(ele)).map(doSomething));
  let sum = out.reduce((a, b) => a + b, 0);
  const out2 = await Promise.all(arr.filter(ele => Array.isArray(ele)).map(calc));
  sum += out2.reduce((a, b) => a + b, 0);
  return sum;
}

const process = async() => {
  console.log('processing..');
  const arr = [1, 2, 3, 4, 5, [6, 7], 1, [8, [10, 11]]];
  const out = await calc(arr);
  console.log(out);
}


process();

【讨论】:

  • doSomething 错字不在原始代码中,+=out2 出现错误
【解决方案2】:

我能否提出一个稍微不同的问题细分?

我们可以编写一个函数,将您的函数递归地应用于数组的所有(嵌套)元素,另一个函数递归地汇总结果。

然后我们await将第一次调用的结果传递给第二次。

我觉得这些功能比较简单,也可以复用。

const doSomething = async (ele) => new Promise(resolve => {
  setTimeout(() => resolve(ele * 2), 1000);
})

const recursiveCall = async (proc, arr) => 
  Promise .all (arr .map (ele => 
    Array .isArray (ele) ? recursiveCall (proc, ele) : proc (ele)
  ))

const recursiveAdd = (ns) =>
  ns .reduce ((total, n) => total + (Array .isArray (n) ? recursiveAdd (n) : n), 0)

const process = async() => {
  console.log('processing..');
  const arr = [1, 2, 3, 4, 5, [6, 7], 1, [8, [10, 11]]];
  const processedArr = await recursiveCall (doSomething, arr);
  const out = recursiveAdd (processedArr)
  console.log(out);
}


process();

【讨论】:

  • 我喜欢这种简单性,但我试图找到一种方法来概括 isArray(x) ? recur(x) : flat(x) 模式...
  • const nested = (flat, recur) => x => isArray(x) ? recur(x) : flat(x) 很有趣,但并不完美。也许我想多了,但我感觉到我们的两个答案都有更广泛的概括。用于嵌套输入的某种转换器,但也足够灵活以提升到异步上下文...
【解决方案3】:

我认为通用的deepReduce 很好地解决了这个问题。请注意,它是以同步形式编写的 -

const deepReduce = (f, init = null, xs = []) =>
  xs.reduce
    ( (r, x) =>
        Array.isArray(x)
          ? deepReduce(f, r, x)
          : f(r, x)
    , init
    )

不过,我们可以通过使用 Promise 初始化并使用 async 函数减少来异步使用 deepReduce -

deepReduce
  ( async (r, x) =>
      await r + await doSomething(x)
  , Promise.resolve(0)
  , input
  )
  .then(console.log, console.error)

在此处查看实际代码 -

const deepReduce = (f, init = null, xs = []) =>
  xs.reduce
    ( (r, x) =>
        Array.isArray(x)
          ? deepReduce(f, r, x)
          : f(r, x)
    , init
    )

const doSomething = x =>
  new Promise(r => setTimeout(r, 200, x * 2))

const input =
  [1, 2, 3, 4, 5, [6,7], 1, [8,[10,11]]]

deepReduce
  ( async (r, x) =>
      await r + await doSomething(x)
  , Promise.resolve(0)
  , input
  )
  .then(console.log, console.error)

// 2 + 4 + 6 + 8 + (10 + 14) + 2 + (16 + (20 + 22))
// => 116

console.log("doing something. please wait...")

进一步概括

我们在上面手动编码了一个求和函数 (+),其中的和为空 0。实际上,这个函数可能更复杂,也许我们想要一个更通用的模式,这样我们就可以分段构建我们的程序。下面我们将同步的add 转换为异步函数使用liftAsync2(add) -

const add = (x = 0, y = 0) =>
  x + y  // <-- synchronous

const main =
  pipe
    ( deepMap(doSomething) // <-- first do something for every item
    , deepReduce(liftAsync2(add), Promise.resolve(0)) // <-- then reduce
    )

main([1, 2, 3, 4, 5, [6,7], 1, [8,[10,11]]])
  .then(console.log, console.error)

// 2 + 4 + 6 + 8 + (10 + 14) + 2 + (16 + (20 + 22))
// => 116

deepMapdeepReduce 泛型。这些是咖喱形式,因此它们可以直接插入pipe,但这只是风格问题 -

const deepReduce = (f = identity, init = null) => (xs = []) =>
  xs.reduce
    ( (r, x) =>
        Array.isArray(x)
          ? deepReduce(f, r)(x)
          : f(r, x)
    , init
    )

const deepMap = (f = identity) => (xs = []) =>
  xs.map
    ( x =>
        Array.isArray(x)
          ? deepMap(f)(x)
          : f(x)
    )

liftAsync2 采用一个通用二进制(有两个参数)函数并将其“提升”到异步上下文中。 pipeidentity 通常在大多数功能库中都可用,或者很容易自己编写 -

const identity = x =>
  x

const pipe = (...fs) =>
  x => fs.reduce((r, f) => f(r), x)

const liftAsync2 = f =>
  async (x, y) => f (await x, await y)

这是您可以自己运行的演示中的所有代码。请注意,因为 deepMap 同步地将 doSomething 应用于所有嵌套元素,所以所有 Promise 都是并行运行的。这与第一个程序中的串行行为形成了直接对比。这可能是可取的,也可能不是可取的,因此了解这些运行方式的差异很重要 -

const identity = x =>
  x

const pipe = (...fs) =>
  x => fs.reduce((r, f) => f(r), x)
    
const liftAsync2 = f =>
  async (x, y) => f (await x, await y)

const deepReduce = (f = identity, init = null) => (xs = []) =>
  xs.reduce
    ( (r, x) =>
        Array.isArray(x)
          ? deepReduce(f, r)(x)
          : f(r, x)
    , init
    )

const deepMap = (f = identity) => (xs = []) =>
  xs.map
    ( x =>
        Array.isArray(x)
          ? deepMap(f)(x)
          : f(x)
    )
    
const doSomething = x =>
  new Promise(r => setTimeout(r, 200, x * 2))

const add =
  (x, y) => x + y

const main =
  pipe
    ( deepMap(doSomething)
    , deepReduce(liftAsync2(add), Promise.resolve(0))
    )

main([1, 2, 3, 4, 5, [6,7], 1, [8,[10,11]]])
  .then(console.log, console.error)

// 2 + 4 + 6 + 8 + (10 + 14) + 2 + (16 + (20 + 22))
// => 116

console.log("doing something. please wait...")

【讨论】:

  • 不错。我用Promises 与.then 一起减少使用,但从未想过以这种方式使用await。但与这里的其他答案相比,有一个真正的缺点。当您需要按顺序解析 Promise 时,这种技术很有效。但是对于这个问题,调用是独立的,可以并行运行。
  • Scott,dangit 我的编辑速度太慢了 9 分钟!昨晚我想扩展这个,但我需要一些睡眠,并且必须保持简短 zzz
  • 对不起。下次我一定要多等十分钟!
  • 哈哈哈我想也许是时候出去呼吸一下新鲜空气了!
  • 注意deepMapanother answer 中的泛化。这可以通过重构使用objectMap(或多态map)函数来实现,但这是此版本的一种可能的泛化......
猜你喜欢
  • 1970-01-01
  • 2019-06-14
  • 1970-01-01
  • 2015-06-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-09-24
  • 2016-11-29
相关资源
最近更新 更多