выход из обратного вызова итератора, используемого внутри генератора

Кто-нибудь пытался заставить Underscore JS или lodash (или любые стандартные функции ES5, если уж на то пошло) работать с генераторами?

Если у нас есть массив var myArray = [1,2,3,4,6];, мы хотим использовать его forEach.

В случае без генератора вы просто

myArray.forEach(function(k) {
  console.log(k);
});

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

var foreach = function* (arr, fn) {
  var i;
  for (i = 0; i < arr.length; i++) {
    yield * fn(arr[i], i);
  }
};

yield* foreach(myArray, function* (k) {
  var a = yield fs.readFile();
});

Какой отстой.

Кто-нибудь знает способ заставить анонимные функции работать с генераторами? Из-за этого мы теряем всю библиотеку lodash.

Примечание. Я использую Traceur для компиляции своего кода в ES6 с включенными генераторами.
Примечание. Я не использую co(). Я использую пользовательскую функцию генератора, показанную ниже.

var run = function(generatorFunction) {
  var generatorItr = generatorFunction(resume);
  function resume(callbackValue) {
    generatorItr.next(callbackValue);
  }
  generatorItr.next();
};

person Sean Clark    schedule 26.07.2014    source источник
comment
Может быть, это только у меня, но я не понимаю, в чем именно проблема. Это больше похоже на то, что проблема заключается в использовании, например. forEach с генераторами.   -  person Felix Kling    schedule 26.07.2014
comment
Ну да, но это не настоящая * проблема. Проблема заключается в использовании yield внутри функции, не являющейся генератором. Который ForEach будет использовать в 90% случаев. Не говоря уже о _.find(), _.filter(), Array.reduce(), Array.forEach(), Array.map(). Все они бесполезны, если вам нужно что-то выдать внутрь.   -  person Sean Clark    schedule 26.07.2014
comment
В случае forEach вы можете просто использовать for (var e of arr) { yield doSomethingWith(e); } или обычный цикл for. Для других методов, таких как filter или reduce, я не вижу, как использование генератора может быть вообще полезным. Обратный вызов filter должен возвращать логическое значение. Как именно использование генератора имеет смысл здесь?   -  person Felix Kling    schedule 26.07.2014
comment
Ну, любой ответ, который я могу придумать, будет неэффективным кодом. Выполнение работы внутри цикла. Но в любом случае, если у вас есть список URL-адресов mp3, и вам нужно отфильтровать этот список до тех, которые действительно существуют в файловой системе. Обычно вы делаете фильтр в своем списке, проверяете FS на каждой итерации и Promise.all(), когда все они будут выполнены. Используя генераторы, мы не можем использовать фильтр. Мы должны зациклиться и сохранить второй массив результатов.   -  person Sean Clark    schedule 26.07.2014
comment
@SeanClark: обратите внимание, что Promise.all запустил бы запросы fs параллельно, в то время как решение генератора, которое вы, кажется, ищете, было бы последовательным.   -  person Bergi    schedule 26.07.2014
comment
Это верно, однако вы можете потом уступить, сделав их снова параллельными. Вы можете поместить запросы fs в массив и получить массив. Подобно обещаниям, но не нужно создавать обещания и разрешать их все, а затем в другом блоке Promise.all   -  person Sean Clark    schedule 28.07.2014


Ответы (1)


Если я правильно понимаю вашу проблему, по сути, вы пытаетесь что-то сделать (повторять до тех пор, пока не будет найдена хорошая точка остановки) асинхронным способом на языке (JS), который действительно разработан для синхронности. Другими словами, в то время как вы обычно можете сделать:

_([1,2,3]).any(function(x) {
    var shouldWeStopLooping = x % 2 == 0;
    return shouldWeStopLogging;
});

вместо этого вы хотите, чтобы код «должны ли мы прекратить зацикливание» прервать нормальное выполнение, а затем вернуться, что невозможно с традиционным JS (yield относительно новый для языка) и, следовательно, невозможно в Underscore/Lodash :

_([1,2,3]).any(function(x) {
    var shouldWeStopLooping = $.ajax(...); // Doesn't work; code keeps going
    return shouldWeStopLogging;
});

Есть два подхода, которые вы можете использовать, ни один из которых не является идеальным.

Как упоминалось в комментариях, одним из подходов было бы сначала выполнить всю вашу «отложенную» работу, а затем выполнить итерацию:

var workInProgress = _([1,2,3]).map(someAjaxOperation);
$.when.apply(workInProgress).done(doSomethingBasedOnAjaxResults);

Но (как также отмечено в комментариях) это не совсем то же самое, поскольку вы выполняете работу AJAX со всеми элементами вашего массива (по сравнению с настоящим генератором, который будет перебирать столько, сколько необходимо, чтобы найти «победитель»).

Другой подход заключается в устранении асинхронности. jQuery позволяет вам передать async: false в запрос AJAX, который «решает» проблему, позволяя вам использовать Underscore/Lodash/что угодно... но он также блокирует браузер вашего пользователя до тех пор, пока он выполняет работу AJAX, что вероятно, это не то, что вы хотите.

К сожалению, если вы хотите использовать такую ​​библиотеку, как Underscore/Lodash, это единственные варианты, которые я вижу. Единственным другим вариантом было бы написать свою собственную смесь Underscore/Lodash, что на самом деле не так уж сложно. Я бы порекомендовал сделать это, так как это позволит вам по-прежнему использовать все другие замечательные функции в этих библиотеках, продолжая выполнять итерации согласованным образом.

person machineghost    schedule 04.08.2014
comment
Не совсем. Мне не нужно останавливать цикл, когда x найден. Я просто хочу использовать функции цикла lodash. Как фильтр и уменьшить. Или ES6 уменьшить в этом отношении. Есть случаи, когда я хочу, чтобы что-то происходило в цикле, который может быть асинхронным, но мне нужен более приятный способ написания этого кода. В этом и смысл всего этого, писать более приятный код. - person Sean Clark; 06.08.2014
comment
Кроме того, я согласен с тем, что ваш jQuery async: false является решением для клиента, но здесь это не совсем так. Я хочу, чтобы все было асинхронно, потому что это NodeJS. Я просто хочу, чтобы код, который я пишу, не попадал в ад обратных вызовов. Я хотел хорошее решение для использования циклов, но сейчас похоже, что yield нельзя использовать в ситуации закрытия, он должен быть внутри функции gen. ES7, похоже, имеет асинхронные функции, что может быть ответом. - person Sean Clark; 06.08.2014