49

Is there any way to get generators into node.js?

I'm currently faking them with callbacks, but I have to remember to check the response of the callback inside of my generator function which creates a lot of if (callback(arg) === false) return;

I want something like in python:

for p in primes():
  if p > 100: break
  do_something(p)

which I'm doing in node like this:

primes(function(p) {
  if (p > 100) return false;
  do_something(p)
});

Maybe something like coffeescript could help?

Paul Tarjan
  • 48,968
  • 59
  • 172
  • 213
  • Coffeescript will probably not get generators anytime soon: https://github.com/jashkenas/coffee-script/issues/issue/983#issue/983/comment/639738 – Amir Jan 13 '11 at 00:16
  • 1
    Also note JavaScript uses camelCase for functions, ie `doSomething`, not `do_something` – mikemaccana Apr 11 '14 at 14:02

10 Answers10

23

Yes, since version 0.11. Enjoy!

http://wingolog.org/archives/2013/05/08/generators-in-v8

http://jlongster.com/A-Study-on-Solving-Callbacks-with-JavaScript-Generators

Wilfred Hughes
  • 29,846
  • 15
  • 139
  • 192
Diego Pino
  • 11,278
  • 1
  • 55
  • 57
  • 4
    but their current verion is `v0.10.24`, I'm confused. – Jürgen Paul Dec 20 '13 at 11:32
  • 2
    The latest stable release is v.0.10.24, but you can always get an unstable release [here](http://nodejs.org/dist/) (currently v0.11.10). The releases are always announced in the node [blog](http://blog.nodejs.org/release/) (with changelog). The v0.11.10 docs are [here](http://nodejs.org/dist/v0.11.10/docs/api/). That shouldn't have been to hard to find, from the [Nodejs.org home page](http://nodejs.org) > [Downloads](http://nodejs.org/download/) > [Other releases](http://nodejs.org/dist/). – Marko Bonaci Jan 11 '14 at 15:10
  • I can't get them to work, even with the `--harmony` flag in node v0.10.29. – mpen Jul 06 '14 at 01:01
  • @Mark, the harmony flag only works on the "unstable" release of Node (v0.11.12). If you'd like to play around with it but still easily switch back to the current "stable" release, I'd recommend installing `nvm` (https://github.com/creationix/nvm or if you use fish shell https://github.com/Alex7Kom/nvm-fish :) ) and installing the latest unstable version and hammer away on those generators. – adr Jul 17 '14 at 02:27
  • @ADRegan Looks like I didn't read anything else in this thread when I wrote that comment; eventually figured out I needed 0.11.x :-) Thanks. I'm a Windows user though; discovered [nodist](https://github.com/marcelklehr/nodist) works pretty well for managing node versions. – mpen Jul 17 '14 at 06:51
  • @Mark Use `--use-strict` and `--harmony` flags together – Gábor Imre Aug 28 '14 at 12:22
8

The answer is "not currently" but Marcel seems to be my hero. Lets hope this goes somewhere:

https://groups.google.com/forum/#!msg/nodejs/BNs3OsDYsYw/oCsWBw9AWC0J https://github.com/laverdet/node-fibers

Paul Tarjan
  • 48,968
  • 59
  • 172
  • 213
4

You can use generators in Node.js, but only in 0.11+. Node.js 0.12 (stable) is now available. Add --harmony_generators or --harmony to the command line parameters of node to enable it.

With Traceur, you can compile advanced JavaScript to vanilla JavaScript. You could make a loader for node.js that does this on-the-fly. Since it runs on, and compiles to vanilla JavaScript, it runs in node.js < 0.11 as well as in the browser.

Facebook has developed a lighter version that only supports generators, called Regenerator. It works similarly to Traceur.

DDS
  • 4,325
  • 22
  • 33
3

Apparently not in the current stable version. You can however achieve the same using node-fibers + promises.

Here is my implementation:

var fiber = require('fibers');

module.exports.yield = function (promise) {

    var currentFiber = fiber.current;
    promise
        .then(function (value) {
            currentFiber.run(value);
        })
        .otherwise(function (reason) {
            currentFiber.throwInto(reason);
        });

    return fiber.yield();
};
module.exports.spawn = function (makeGenerator) {
    fiber(function () {
        makeGenerator.apply(this, Array.prototype.slice.call(arguments, 1));
    }).run();
};

And a sample code on how it works: (query.find returns a promise)

        var generators = require('./utils/generators');
        var query = require('./utils/query');

        generators.spawn(function () {
            try {
                var field1 = generators.yield(query.find('user', { _id : '1' }));
                var field2 = generators.yield(query.find('user', { _id : '2' }));
                console.log('success', field1[0]._id, field2[0]._id);
            }
            catch (e) {
                console.error('error', e);
            }
        });
Peeter
  • 9,282
  • 5
  • 36
  • 53
  • could you please also post the source code for query? will it work with any callback system? – kroe Nov 05 '14 at 15:18
2

The issue proposing generatiors in v8 has recently been accepted by v8 project member.
Please vote there to make yield come true.

disfated
  • 10,633
  • 12
  • 39
  • 50
2

You might check out wu.js at http://fitzgen.github.com/wu.js/ It has lots of interesting iterator functions.

2

Yes and no.

var myGen = (function () {
    var i = 0;
    return function () {
        i++; return i; }
})();
var i;
while ((i = myGen()) < 100 ) {
    do something; }

As you see, you can implement something like one using closures, but it does not have native generators.

Not a Name
  • 993
  • 7
  • 18
0

We are using gnode for generators in node < 0.11.3 - https://github.com/TooTallNate/gnode

manojlds
  • 290,304
  • 63
  • 469
  • 417
0

Update 2014: Node does support callbacks now. The following is a post from 2010.


You should use callbacks. If the function does something asynchronously, you may also want a continuation callback (continuation is a bad word, since it also means something else, but you get my point.)

primes(function(p) {
  if (p > 100) return false // i assume this stops the yielding
  do_something(p)
  return true // it's also better to be consistent
}, function(err) { // fire when the yield callback returns false
  if (err) throw err // error from whatever asynch thing you did
  // continue...
})

Updated with example code

I flipped it, so that it returns true on complete (since null, false and undefined all evaluate to false anyways).

function primes(callback) {
  var n = 1, a = true;
  search: while (a)  {
    n += 1;
    for (var i = 2; i <= Math.sqrt(n); i += 1)
      if (n % i == 0)
        continue search;
    if (callback(n)) return
  }
}

primes(function(p) {
  console.log(p)
  if (p > 100) return true
})
Bill the Lizard
  • 398,270
  • 210
  • 566
  • 880
Tor Valamo
  • 33,261
  • 11
  • 73
  • 81
  • But then my `primes` function is littered with `if (callback(arg) === false) return;`instead of just `yield arg`. Is it supposed to be that ugly? – Paul Tarjan Nov 12 '10 at 06:27
  • 1
    `do { /* setup callback data */ } while(callback(arg)); continuation()` ? Remember that it's not as important what it looks like inside the function, as long as the interface and the output is good. – Tor Valamo Nov 12 '10 at 22:16
  • oh, and regarding your primes function (i assume you're doing some complicated nesting there), you'll need to code it in such a way that it can drop everything, move to callback, then start again on the next iteration (using temporary variables to keep state), or you'll just have to live with the multiple callback lines. – Tor Valamo Nov 12 '10 at 22:20
0

Yes Node.js and JavaScript now have both synchronous iterators (as of atleast Node v6) and asynchronous iterators (as of Node v10):

An example generator/iterator with synchronous output:

// semi-pythonic like range
function* range(begin=0, end, step=1) {
  if(typeof end === "undefined") {
    end = begin;
    begin = 0;
  }
  for(let i = begin; i < end; i += step) {
    yield i;
  }
}

for(const number of range(1,30)) {
  console.log(number);
}

A similar async generator/iterator.

const timeout = (ms=1000) => new Promise((resolve, reject) => setTimeout(resolve, ms));

async function* countSeconds(begin=0, end, step=1) {
  if(typeof end === "undefined") {
    end = begin;
    begin = 0;
  }
  for(let i = begin; i < end; i += step) {
    yield i;
    await timeout(1000);
  }
}

(async () => {
  for await (const second of countSeconds(10)) {
    console.log(second);
  }
})();

There is a lot to explore here are some good links. I will probably update this answer with more information later:

John
  • 7,114
  • 2
  • 37
  • 57