Рекурсия - это функциональное наследие, поэтому использование ее в функциональном стиле дает наилучшие результаты. Это означает написание функций, которые принимают и обрабатывают свои входные данные (а не полагаются на внешнее состояние) и возвращают значения (вместо того, чтобы полагаться на мутацию или побочные эффекты).
С другой стороны, ваша программа вызывает функции без аргументов, использует внешнее состояние missingItems
, products
, productsLength
, total
, page
и использует мутации, такие как page++
, и переназначения, такие как * 1009. *, productsLength = ...
, missingItems = ...
. Мы исправим все это!
Я просто собираюсь пробиться сквозь это и надеюсь, что это направит вас на правильный путь. Если вы застряли в конце, я привожу некоторые другие ответы, которые более подробно объясняют методы, используемые здесь.
const getAllProducts = async (page = 0) =>
asyncUnfold
( async (next, done, [ res, nextPage ]) =>
res.products.length === 0
? done ()
: next ( res.products // value to add to output
, [ await getPage (nextPage), nextPage + 1 ] // next state
)
, [ await getPage (page), page + 1 ] // initial state
)
Мы представляем getPage
помощник, который мы использовали выше
const getPage = async (page = 0, itemsPerPage = 5) =>
getProducts (page * itemsPerPage, itemsPerPage)
.then (res => res.json ())
Далее, для целей этой демонстрации, мы вводим фальшивую функцию getProducts
и фальшивую DB
, где каждый продукт представляет собой просто число. Мы также используем delay
для имитации реальной задержки в сети.
В вашей реальной программе вам просто нужно предоставить функцию getProducts
, которая может запрашивать продукты, используя offset
и limit
входные данные
const getProducts = (offset = 0, limit = 1) =>
Promise.resolve
({ json: () =>
({ products: DB.slice (offset, offset + limit) })
})
.then (delay)
const delay = (x, ms = 250) =>
new Promise (r => setTimeout (r, ms, x))
const DB =
[ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20
, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30
, 31, 32, 33
]
Ниже мы покажем, как работает программа. getAllProducts
- это знакомая асинхронная функция, которая возвращает Promise своего результата. Мы объединяем вызов .then
, чтобы видеть все страницы продукта, выводимые в консоли
getAllProducts () .then (console.log, console.error)
// ~2 seconds later
// [ [ 1, 2, 3, 4, 5 ]
// , [ 6, 7, 8, 9, 10 ]
// , [ 11, 12, 13, 14, 15 ]
// , [ 16, 17, 18, 19, 20 ]
// , [ 21, 22, 23, 24, 25 ]
// , [ 26, 27, 28, 29, 30 ]
// , [ 31, 32, 33 ]
// ]
Вместо группировки товаров по страницам было бы хорошо, если бы мы могли вернуть все товары в одном массиве. Мы можем немного изменить getAllProducts
, чтобы добиться этого
const concat = (xs, ys) =>
xs .concat (ys)
const concatAll = (arrays) =>
arrays .reduce (concat, [])
const getAllProducts = async (page = 0) =>
asyncUnfold
( ... )
<b>.then (concatAll)</b>
getAllProducts () .then (console.log, console.error)
// ~2 seconds later
// [ 1, 2, 3, 4, 5, 6, 7, ..., 31, 32, 33 ]
Наконец, мы вводим asyncUnfold
const asyncUnfold = async (f, initState) =>
f ( async (value, nextState) => [ value, ...await asyncUnfold (f, nextState) ]
, async () => []
, initState
)
Полная демонстрация программы
// dependencies -------------------------------------------------
const asyncUnfold = async (f, initState) =>
f ( async (value, nextState) => [ value, ...await asyncUnfold (f, nextState) ]
, async () => []
, initState
)
const concat = (xs, ys) =>
xs .concat (ys)
const concatAll = (arrays) =>
arrays .reduce (concat, [])
// fakes --------------------------------------------------------
const getProducts = (offset = 0, limit = 1) =>
Promise.resolve
({ json: () =>
({ products: DB.slice (offset, offset + limit) })
})
.then (delay)
const delay = (x, ms = 250) =>
new Promise (r => setTimeout (r, ms, x))
const DB =
[ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20
, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30
, 31, 32, 33
]
// actual program
const getAllProducts = async (page = 0) =>
asyncUnfold
( async (next, done, [ res, nextPage ]) =>
res.products.length === 0
? done ()
: next ( res.products
, [ await getPage (nextPage), nextPage + 1 ]
)
, [ await getPage (page), page + 1 ]
)
.then (concatAll)
const getPage = async (page = 0, itemsPerPage = 5) =>
getProducts (page * itemsPerPage, itemsPerPage)
.then (res => res.json ())
// demo ---------------------------------------------------------
getAllProducts ()
.then (console.log, console.error)
// ~2 seconds later
// [ 1, 2, 3, ..., 31, 32, 33 ]
Другие вопросы, которые я ответил о рекурсии и обещаниях
Асинхронность и рекурсия являются отдельными понятиями. Если вы боретесь с asyncUnfold
, это может помочь сначала понять его синхронный аналог unfold
. Эти вопросы и ответы могут помочь отличить их.