5

I have an api thats going to return a cursor for fetching more data. I've mocked it out like this:

function fetch(n) {
  return Promise.resolve({
    results: [n],
    next: next < 10 && n + 1,
  })
}

What I'm trying to do is figure out how I can use async/await along with generators in order to interact with this api.

Here's basically what I've prototyped:

async function* api(url) {
  let result = await fetch(url)
  yield result
  while (result.next) {
    result = await fetch(result.next)
    yield result
  }
}

The idea is that I should be able to create an async generator and yield from that generator in order to iterate through the cursor:

async function main() {
  const gen = api(0)
  const zero = await gen.next()
  console.log(zero.result)
  const one = await gen.next()
  console.log(one.result)
  const rest = await Promise.all([...gen])
  console.log(rest.map(r => r.result))
}

All things considered, I think this is a pretty sweet way of handling paginated data and being able to pull out all of the data with [...gen] is pretty damn cool.

Only problem is, it doesn't work! Apprently you can't use async with function*:

❯❯❯ node --version
v7.0.0
❯❯❯ node --harmony --harmony-async-await async-generator.js
/Users/chetcorcos/code/async-generator.js:11
async function* api(url) {
              ^
SyntaxError: Unexpected token *
    at Object.exports.runInThisContext (vm.js:76:16)
    at Module._compile (module.js:545:28)
    at Object.Module._extensions..js (module.js:582:10)
    at Module.load (module.js:490:32)
    at tryModuleLoad (module.js:449:12)
    at Function.Module._load (module.js:441:3)
    at Module.runMain (module.js:607:10)
    at run (bootstrap_node.js:382:7)
    at startup (bootstrap_node.js:137:9)
    at bootstrap_node.js:497:3

But I really feel like this should be possible. There's a popular library called co that I've been poking around with but I don't think that's what I want.

Any ideas how to get this concept of "async generators" to work?

Chet
  • 18,421
  • 15
  • 69
  • 113
  • 5
    Have a look at the upcoming proposal https://github.com/tc39/proposal-async-iteration . You can use it with Babel's [`babel-plugin-transform-async-generator-functions`](https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-async-generator-functions) transform – Felix Kling Dec 27 '16 at 19:14
  • Is using `async/await` part of requirement? – guest271314 Dec 27 '16 at 19:16
  • @FelixKling that proposal is epic. Thanks! – Chet Dec 27 '16 at 19:28
  • @guest271314 not necessarily part of the requirement -- I've been playing around trying to make it happen without the keywords, but its been a struggle. – Chet Dec 27 '16 at 19:30
  • The proposal is at stage 3 btw, so it has good chances becoming part of the spec in 2018 (I don't think it will be part of 2017). – Felix Kling Dec 27 '16 at 19:37
  • We can use --js-flags=--harmony-async-iteration now in Chrome Canary but the same flag does not seem to work for Node.js. Thoughts? I was under the impression that they shared javascript engines, but maybe I'm inventing that thought. –  Jun 21 '17 at 16:42
  • 1
    you can down-compile with typescript or simply run natively with nodejs9 with the `--harmony` flag: https://stackoverflow.com/questions/43694281/ts2318-cannot-find-global-type-asynciterableiterator-async-generator/43694282#43694282 – Meirion Hughes Dec 15 '17 at 10:42
  • You can use node 10, node 9 (under the --harmony flag), or use a helper function. https://stackoverflow.com/questions/48101479/javascript-async-generator/52900682#52900682 – Doug Coburn Oct 20 '18 at 00:05

2 Answers2

2

You can do this using the Babel plugin transform-async-generator-functions.

The usage is like this:

const g = async i => [ 1, 2, 3 ]
  .map(x => x * 10 ** i);

const f = async function * () {
  for (let i = 0; i < 10; i++) {
    const xs = await g(i);
    for (const x of xs) {
      yield x;
    }
  }
};

const main = async () => {
  for await (const x of f()) {
    console.log(x);
  }
};

main().catch(e => console.error(e));

Here is an example repo showing how to setup your project.

The important part is the .babelrc file:

{
  "presets": [ "env" ], 
  "plugins": [ "transform-async-generator-functions" ]
 }
sdgfsdh
  • 33,689
  • 26
  • 132
  • 245
0

You can pass call the generator function as parameter to without using spread element, Promise.all() accepts an iterable as parameter which yield returns. Note, Promise.all() does not resolve or reject the passed Promise objects in sequential order, though does return the resulting array in same order as the elements within passed iterable.

let api = (value) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(value)
    }, Math.floor(Math.random() * 3500))
  })
};

let values = [1, 2, 3];
let results = [];
let gen = function* gen(fn, props) {
  let i = 0; 
  do {
    yield fn(props[i]).then(res => {console.log(res); return res});
    ++i;
  } while (i < props.length);
}

Promise.all(gen(api, values))
.then(data => console.log("complete:", data))
.catch(err => console.log(err));
guest271314
  • 1
  • 15
  • 104
  • 177
  • This looks like it could be a good answer... but the wording is unclear: "You can pass call the generator function as parameter to without using spread element" – Zach Smith Mar 11 '18 at 10:09