Использование Promise.Все рекурсия не работает - PullRequest
2 голосов
/ 02 мая 2020

Фактическая doSomething функция отправляет ele в удаленный API для выполнения некоторых вычислений.

Моя функция cal c, предназначенная для суммирования вычислений удаленного 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();

Ответы [ 3 ]

3 голосов
/ 03 мая 2020

Могу ли я предложить несколько иную разбивку задачи?

Мы можем написать одну функцию, которая рекурсивно применяет вашу функцию ко всем (вложенным) элементам вашего массива, а другую - для рекурсивного суммирования результатов.

Затем мы 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();
3 голосов
/ 02 мая 2020

Хотя может показаться, что я рассмотрел несуществующие проблемы - исходный код вопроса содержал ВСЕ FL aws, которые я адресую в этом ответе, включая Второй и Третий ниже

да, код в вопросе теперь работает! Но это явно было ошибкой

Во-первых: нет необходимости в конструкторе Promise в функции cal c, так как вы используете Promise.all, который возвращает обещание, если вы делаете cal c asyn c просто используйте await

Секунда: dosomething! == doSomething

Третий: out2 - это массив, так что sum += out2 собирается вас испортить

Четвертый: .map(ele => doSomething(ele)) можно записать .map(doSoemthing) - и то же самое для calc(ele) map

Итак, рабочий код становится:

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();
2 голосов
/ 03 мая 2020

Я думаю, что дженерик 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...")
...