3

I've sometimes found myself wanting an asychronous version of unfold in Javascript: a function that would take a value, then apply an async function to it to get another value, and repeat on this value until some condition applies, producing an array (/ stream / iterator) of values.

E.g.

const res = await asyncUnfold(x=>Promise.resolve(x+1), x => x<6, 2) 
console.log(res)  // [2,3,4,5]

(real examples would do meaningful async operations: e.g. making a repeated network call based on some seed value, and recording all results.)

Does anyone know of a good implementation of this? I can use an async generator or async while loop to do the work, but I'd rather not have to think about coding that each time I want a similar operation. Perhaps there's a good implementation on NPM already...

(see https://nabilhassein.github.io/blog/unfold/ and https://en.wikipedia.org/wiki/Anamorphism and https://raganwald.com/2016/11/30/anamorphisms-in-javascript.html for some background on unfold. Ramda has an implementation of unfold, but it doesn't seem to do async operations. P-iterator and Rubico have async array operations, but neither seem to include unfold).

EDIT: in some cases, and perhaps in general, it might be better to NOT include the initial (seed) value in the returned array: e.g.

const res = await asyncUnfoldExclusive(x=>Promise.resolve(x+1), x => x<6, 2) 
console.log(res)  // [3,4,5]
phhu
  • 1,462
  • 13
  • 33
  • 1
    Should be quite easy with a loop in an `async` function. No need to involve generators. – Bergi Mar 24 '22 at 15:28
  • 1
    `const asyncUnfold = async (f, c, s) => { const res = []; let current = s; while (c(current)) { res.push(current); current = await f(current); } return res; };`? – ASDFGerte Mar 24 '22 at 15:29
  • (potentially with another `await` in front of `c(current)`, depending on whether the condition may be async) – ASDFGerte Mar 24 '22 at 15:35
  • Thanks. Yes, that kind of pattern works to solve my immedate problem: it's more that I want a functional-programming style implementation to save me having to think: or perhaps to encourage me to think in a more abstract way: it's a pattern I have implemented various times, but I sometimes include specifics of the task-and-hand in the implementation. – phhu Mar 24 '22 at 15:35
  • @phhu "*save me having to think*" - no, it won't do that. It will only allow you to write shorter code that focuses on the specifics – Bergi Mar 24 '22 at 15:37
  • "shorter code that focuses on the specifics": that'll do as well ;-) – phhu Mar 24 '22 at 15:41
  • 2
    See also, an [excellent answer](https://stackoverflow.com/a/49543489/1243641) from several years back. Async is only one of its concerns, but it ties a number of concerns together very nicely. – Scott Sauyet Mar 24 '22 at 16:43

2 Answers2

2

You can just take the implementation from the article you linked and slap async/await on it:

async function asyncUnfold(f, seed) { /*
^^^^^ */
  var arr = [];
  var state = seed;

  var next;
  while (next = await f(state)) {
//              ^^^^^
    state = next[1];
    arr.push(next[0]);
  }

  return arr;
}

Or using the three-parameter style from your question,

async function asyncUnfold(step, pred, value) {
  const res = [];
  while (pred(value)) {
    res.push(value);
    value = await step(value);
  }
  return res;
}

or implemented in a recursive style

async function asyncUnfold(step, pred, value) {
  return pred(value)
    ? [value, ...await asyncUnfold(step, pred, await step(value))]
    : [];
}
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • "You can just take the implementation from the article you linked and slap async/await on it:".... love it! – phhu Mar 24 '22 at 15:50
2

Here is a functional style solution to support the example you have given:

const asyncUnfold = (next, predicate, start) =>
    !predicate(start) ? Promise.resolve([]) 
    : next(start).then(value => asyncUnfold(next, predicate, value))
                 .then(arr => [start, ...arr]);

asyncUnfold(x => Promise.resolve(x+1), x => x<6, 2).then(console.log);   // [2,3,4,5]
trincot
  • 317,000
  • 35
  • 244
  • 286