【问题标题】:Looping Over Async Calls by Emulating .reduce() with async/await通过使用 async/await 模拟 .reduce() 来循环异步调用
【发布时间】:2021-05-03 20:49:44
【问题描述】:

代码中使用的变量和函数:

const fakeAPI = (delay, value) =>
 new Promise(resolve => setTimeout(() => resolve(value), delay));

const useResult = x => console.log(new Date(), x);

此代码通过使用 reduce() 实现来打印 forEach 不能使用异步调用的内容:

const forEachAsync = (arr, fn) =>
 arr.reduce(
 (promise, value) => promise.then(() => fn(value)),
 Promise.resolve()
 );
(async () => {
 console.log("START FOREACH VIA REDUCE");
 await forEachAsync([1, 2, 3, 4], async n => {
 const x = await fakeAPI(n * 1000, n);
 useResult(x);
 });
 console.log("END FOREACH VIA REDUCE");
})();

输出

/*
START FOREACH VIA REDUCE
2019-10-13T20:02:23.437Z 1
2019-10-13T20:02:24.446Z 2
2019-10-13T20:02:25.949Z 3
2019-10-13T20:02:27.952Z 4
END FOREACH VIA REDUCE
*/

主代码块上下文中arr.reduce函数中promise和value参数的作用是什么?

value参数是否传递给async n箭头函数?

尽管,我更喜欢解释,因为我正在研究函数式编程,但是,如果有任何其他方式以 FP 方式处理这个问题,我将不胜感激。

【问题讨论】:

  • @SlavaKnyazev 非常感谢。你对这些问题有什么意见吗?我很想听听。
  • 我很难理解你在问什么。您是否正在寻找有关如何枚举异步对象或 reduce 如何工作的说明?
  • 基本上,我要问的是自定义forEachAsync函数在第二个代码块中是如何工作的?
  • @afyonkes 你知道reduce 是如何与同步函数一起工作的吗?以及它如何扩展为调用链?

标签: javascript async-await functional-programming emulation reduce


【解决方案1】:

forEach

forEach 是一个副作用,您正在学习的代码很糟糕。该示例使用asyncawait,因此使用.reduce 链接.then 调用没有意义。也就是说,如果您想体验它的使用痛苦,我们可以实现它 -

async function forEach (arr, fn)
{ for (const x of arr)
    await fn(x)         // <- return value of fn disappears
}

const sleep = ms =>
  new Promise(r => setTimeout(r, ms))

const fakeApi = x =>
  sleep(1000).then(_ => `api response: ${x * x}`)

forEach
  ( [1,2,3,4]
  , async x =>
      // side effect tangled with task processing code
      console.log((new Date).toUTCString(), await fakeApi(x))
  )
  .then(console.log, console.error) // <- no values beyond this point
"Sat, 30 Jan 2021 16:46:35 GMT" "api response: 1"   // <- :35
"Sat, 30 Jan 2021 16:46:36 GMT" "api response: 4"   // <- :36
"Sat, 30 Jan 2021 16:46:37 GMT" "api response: 9"   // <- :37
"Sat, 30 Jan 2021 16:46:38 GMT" "api response: 16"  // <- :38
undefined

要解决上述问题,请注意 serialparallel(下)如何将数据包含在 承诺中,而不是使用诸如 console.log 或 @987654331 之类的副作用推出值@。

连续剧

我们可以写serial串行顺序处理任务-

async function serial (arr, fn)
{ let r = []
  for (const x of arr)
    r.push(await fn(x))
  return r
}

const sleep = ms =>
  new Promise(r => setTimeout(r, ms))

const fakeApi = x =>
  sleep(1000).then(_ => `api response: ${x * x}`)

serial
  ( [1,2,3,4]
  , async x => [ (new Date).toUTCString(), await fakeApi(x) ]
  )
  .then(console.log, console.error)
[
  [
    "Sat, 30 Jan 2021 16:39:04 GMT",  // <- :04
    "api response: 1"
  ],
  [
    "Sat, 30 Jan 2021 16:39:05 GMT",  // <- :05
    "api response: 4"
  ],
  [
    "Sat, 30 Jan 2021 16:39:06 GMT",  // <- :06
    "api response: 9"
  ],
  [
    "Sat, 30 Jan 2021 16:39:07 GMT",  // <- :07
    "api response: 16"
  ]
]

并行

或者我们可以写parallel来处理并行中的任务-

const parallel = (arr, fn) =>
  Promise.all(arr.map(v => fn(v)))

const sleep = ms =>
  new Promise(r => setTimeout(r, ms))

const fakeApi = x =>
  sleep(1000).then(_ => `api response: ${x * x}`)

parallel
  ( [1,2,3,4]
  , async x => [ (new Date).toUTCString(), await fakeApi(x) ]
  )
  .then(console.log, console.error)
[
  [
    "Sat, 30 Jan 2021 16:40:47 GMT",  // <- :47
    "api response: 1"
  ],
  [
    "Sat, 30 Jan 2021 16:40:47 GMT",  // <- :47
    "api response: 4"
  ],
  [
    "Sat, 30 Jan 2021 16:40:47 GMT",  // <- :47
    "api response: 9"
  ],
  [
    "Sat, 30 Jan 2021 16:40:47 GMT",  // <- :47
    "api response: 16"
  ]
]

【讨论】:

  • 这是我猜的更简洁的代码版本。对于像我这样的初学者来说,我的例子太复杂了。理解您的代码要容易得多。正如作者所说,实现 reduce 的想法是因为如果我没记错的话,forEach 会在异步函数中改变对象。从一开始就努力保持纯洁。不希望我的代码到处都是。感谢您的宝贵时间。
  • @afyonkes 很高兴我能为您揭开复杂性。 forEach 本身不会改变任何东西,但在 forEach 回调中使用突变(和其他副作用)是很常见的。事实上forEach 除了触发副作用之外没有其他目的。异步函数也对您是否改变其中的对象没有意见。这完全取决于您如何使用它们。如果您有任何其他问题,我很乐意为您提供帮助。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-12-30
  • 2017-04-29
  • 2017-05-13
  • 1970-01-01
  • 2019-07-16
  • 1970-01-01
相关资源
最近更新 更多