Как имитировать выход JavaScript?

Одним из новых механизмов, доступных в JavaScript 1.7, является yield, полезный для генераторов и итераторов.

В настоящее время это поддерживается только в браузерах Mozilla (что я знаю). Каковы некоторые способы моделирования этого поведения в браузерах, где он недоступен?

Ответы

Ответ 1

Ну, вы всегда можете написать внешнюю функцию, которая инициализирует переменные в закрытии, а затем возвращает объект, который делает любую работу, которую вы хотите.

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

Теперь вы можете написать:

var gen = fakeGenerator(10);

а затем вызовите gen.next() снова и снова. Было бы сложно смоделировать "окончательное" поведение метода "close()" на реальных генераторах, но вы могли бы оказаться где-то близко.

Ответ 2

Как и для ответа Pointy, но с помощью метода hasNext:

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++];
        }
    };
};

Метод hasNext позволяет вам делать петлю следующим образом:

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

Ответ 3

Для нетривиальной функции генератора вы захотите использовать какой-то инструмент для перевода кода в эквивалент ES3, чтобы он мог запускаться в любом современном браузере. Я рекомендую попробовать Traceur, который можно грубо описать как переводчик источника ES6-ES3. Поскольку генераторы являются языковой функцией, предназначенной для ES6, Traceur сможет перевести их для вас.

Traceur предоставляет демонстрационную страницу где вы можете ввести код ES6 и увидеть ES3, сгенерированный "на лету" . Если вы введете что-то простое:

// Note that this declaration includes an asterisk, as specified by current ES6
// proposals. As of version 16, Firefox 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); 
}

вы увидите, что эквивалентный код ES3 является нетривиальным, и для него требуется traceur.runtime, чтобы код работал правильно в браузере. Время выполнения определено в http://traceur-compiler.googlecode.com/git/src/runtime/runtime.js, которое в настоящее время составляет 14K (unminified). Это нетривиальное количество кода, хотя, вероятно, большая часть его может быть оптимизирована с помощью компилятора Closure.

Обратите внимание, что также имеется ошибка, связанная с возможностью встраивания необходимых функций из пространства имен traceur.runtime, что исключает необходимость включения runtime.js в целом: https://code.google.com/p/traceur-compiler/issues/detail?id=119.

Ответ 4

Без какого-либо компилятора или препроцессора... Нет.

Ближе всего вы можете найти что-то вроде этого:

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;
}

Но, ну... Это довольно дерьмово, и на самом деле это не большая замена.

Ответ 5

Я начал небольшой проект, который пытается сделать это с помощью какой-либо обратной связи. Поскольку невозможно создать настоящие сопрограммы в "стандартном" JavaScript, это не происходит без нескольких оговорок, например:

  • невозможно сделать это, следуя протоколу итератора (например, .next() и т.д.),
  • невозможно выполнить итерацию сразу нескольких генераторов,
  • вы должны следить за тем, чтобы не допустить, чтобы неправильные объекты покидали область генератора (например, вызывая yield в тайм-ауте – поскольку это "простой" JavaScript, нет синтаксического ограничения, которое мешает вам это делать),
  • исключения в генераторе немного сложны,
  • и, что не менее важно, это очень экспериментально (только началось это несколько дней назад).

С яркой стороны у вас есть yield!:)

Пример Fibonacci из MDC-страницы будет выглядеть следующим образом:

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());

Вывод:

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

Проект находится на BitBucket в https://bitbucket.org/balpha/lyfe.