Я думаю, что дженерик c deepReduce
хорошо решает эту проблему. Обратите внимание, что он записан в синхронной форме -
const deepReduce = (f, init = null, xs = []) =>
xs.reduce
( (r, x) =>
Array.isArray(x)
? deepReduce(f, r, x)
: f(r, x)
, init
)
Тем не менее, мы можем использовать deepReduce
асинхронно, инициализируя с обещанием и уменьшая с помощью функции async
-
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
deepMap
и deepReduce
. Они в форме карри, поэтому они могут подключаться непосредственно к 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
принимает общую двоичную (имеет два параметра) функцию и «поднимает» это в асинхронный контекст. pipe
и identity
обычно доступны в большинстве функциональных библиотек или их легко написать самостоятельно -
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
ко всем вложенным элементам, все обещания выполняются параллельно. Это прямо противоположно последовательному поведению в первой программе. Это может или не может быть желательно, поэтому важно понимать разницу в том, как они работают -
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...")