I\'ve taken a look at a few questions on recursion in promises and am confused on how to implement them properly:
it would be better if you made i
a parameter of the function instead of relying upon external state
const countToTen = (i = 0) =>
new Promise ((resolve, _) =>
i < 10
? (console.log (i), resolve (countToTen (i + 1)))
: resolve (i))
countToTen () .then (console.log, console.error)
// 0 1 2 3 4 5 6 7 8 9 10
And even better if you made 10
a parameter too
const countTo = (to, from = 0) =>
new Promise ((resolve, _) =>
from < to
? (console.log (from), resolve (countTo (to, from + 1)))
: resolve (from))
countTo (7, 2) .then (console.log, console.error)
// 2 3 4 5 6 7
A more generic approach is a reverse fold - or unfold
const unfold = (f, init) =>
f ( (x, acc) => [ x, ...unfold (f, acc) ]
, () => []
, init
)
const countTo = (to, from = 0) =>
unfold
( (next, done, acc) =>
acc <= to
? next (acc, acc + 1)
: done ()
, from
)
console.log (countTo (10))
// [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
console.log (countTo (7, 2))
// [ 2, 3, 4, 5, 6, 7 ]
But you want a asynchronous unfold, asyncUnfold
. Now the user-supplied function f
can be async and we get a Promise of all collected values
const asyncUnfold = async (f, init) =>
f ( async (x, acc) => [ x, ...await asyncUnfold (f, acc) ]
, async () => []
, init
)
const delay = (x, ms = 50) =>
new Promise (r => setTimeout (r, ms, x))
const countTo = (to, from = 0) =>
asyncUnfold
( async (next, done, acc) =>
acc <= to
? next (await delay (acc), await delay (acc + 1))
: done ()
, from
)
countTo (10) .then (console.log, console.error)
// [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
countTo (7, 2) .then (console.log, console.error)
// [ 2, 3, 4, 5, 6, 7 ]
Here's a more practical example where we have a database of records and we wish to perform a recursive look-up, or something...
db.getChildren
accepts a node id
and returns only the node's immediate children
traverse
accepts a node id
and it recursively fetches all descendant children (in depth-first order)
const data =
{ 0 : [ 1, 2, 3 ]
, 1 : [ 11, 12, 13 ]
, 2 : [ 21, 22, 23 ]
, 3 : [ 31, 32, 33 ]
, 11 : [ 111, 112, 113 ]
, 33 : [ 333 ]
, 333 : [ 3333 ]
}
const db =
{ getChildren : (id) =>
delay (data [id] || [])
}
const Empty =
Symbol ()
const traverse = (id) =>
asyncUnfold
( async (next, done, [ id = Empty, ...rest ]) =>
id === Empty
? done ()
: next (id, [ ...await db.getChildren (id), ...rest ])
, [ id ]
)
traverse (0) .then (console.log, console.error)
// [ 0, 1, 11, 111, 112, 113, 12, 13, 2, 21, 22, 23, 3, 31, 32, 33, 333, 3333 ]