开发者

How to simulate JavaScript yield?

One of the new mechanisms available in JavaScript 1.7 is yield, useful for generators and iterators.

This is currently supported in Mozilla browsers only (that I'm 开发者_开发知识库aware of). What are some of the ways to simulate this behavior in browsers where it is not available?


Well you could always write an outer function that initializes variables in a closure and then returns an object that does whatever work you want.

function fakeGenerator(x) {
  var i = 0;
  return {
    next: function() {
      return i < x ? (i += 1) : x;
    }
  };
}

Now you can write:

var gen = fakeGenerator(10);

and then call gen.next() over and over again. It'd be tricky to simulate the "finally" behavior of the "close()" method on real generators, but you might be able to get somewhere close.


Similar to Pointy's answer, but with a hasNext method:

MyList.prototype.iterator = function() { //MyList is the class you want to add an iterator to

    var index=0;
    var thisRef = this;

    return {
        hasNext: function() {
            return index < thisRef._internalList.length;
        },

        next: function() {
            return thisRef._internalList[index++];
        }
    };
};

The hasNext method let's you loop like:

var iter = myList.iterator() //myList is a populated instance of MyList
while (iter.hasNext())
{
    var current = iter.next();
    //do something with current
}


For a non-trivial generator function, you will want to use some sort of tool to translate your code to an ES3 equivalent so that it can run in any modern browser. I recommend trying out Traceur, which can roughly be described as an ES6-to-ES3 source translator. Because generators are a language feature destined for ES6, Traceur will be able to translate them for you.

Traceur provides a demo page where you can type ES6 code and see the ES3 generated on the fly. If you enter something as simple as:

// Note that this declaration includes an asterisk, as specified by current ES6
// proposals. As of version 16, Firefox's built-in support for generator
// functions does not allow the asterisk.
function* foo() {
  var n = 0;
  if (n < 10) {
    n++;
    yield n;
  }
}

for (var n of foo()) {
  console.log(n); 
}

you will see that the equivalent ES3 code is non-trivial, and it requires traceur.runtime to be included so that the code runs correctly in a browser. The runtime is defined in http://traceur-compiler.googlecode.com/git/src/runtime/runtime.js, which is currently 14K (unminified). This is a non-trivial amount of code, though likely much of it can be optimized away using the Closure Compiler.

Note there is also a bug filed to provide an option to inline the required functions from the traceur.runtime namespace, which would eliminate the need to include runtime.js altogether: https://code.google.com/p/traceur-compiler/issues/detail?id=119.


Without some sort of compiler or preprocessor… No.

The closest you can come is something like this:

function doStuff() {
    var result = { };
    function firstStuf() { ...; result.next = secondStuff; return 42; };
    function secondStuf() { ...; result.next = thirdStuff; return 16; };
    function thirdStuf() { ...; result.next = null; return 7; };
    result.next = firstStuff;
    return result;
}

But, well… That's pretty crappy, and really isn't much of a substitute.


I have started a little project that tries to do this with some callback trickery. Since it's impossible to create real coroutines in "standard" JavaScript, this doesn't come without a few caveats, e.g:

  • it's impossible to make this follow the iterator protocol (stuff like .next() etc.),
  • it's impossible to iterate through several generators at once,
  • you have to watch out not to let the wrong objects leave the generator's scope (e.g. by calling yield in a timeout – since this is "plain" JavaScript, there's no syntax restriction that prevents you from doing this),
  • exceptions in the generator are a little tricky,
  • and last not least, it's very experimental (just started this a few days ago).

On the bright side, you have yield! :)

The Fibonacci example from the MDC page would look like this:

var fibonacci = Generator(function () {
  var fn1 = 1;
  var fn2 = 1;
  while (1){
    var current = fn2;
    fn2 = fn1;
    fn1 = fn1 + current;
    this.yield(current);
  }
});

console.log(fibonacci.take(10).toArray());

Output:

[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

The project is on BitBucket at https://bitbucket.org/balpha/lyfe.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜