Как генераторы ES6 помогают с неблокирующим кодом?

Я некоторое время экспериментировал с генераторами ES6 в Node, и все еще есть одна проблема, которую я не понимаю.

В обычном узле обратного вызова получение значения из базы данных и параллельное выполнение чего-то еще будет выглядеть примерно так:

function executeBoth(){

  db.find("value", function(results){
    console.log(results);
  });

  doSomethingElse("else", function(data){
    console.log(data);
  });
}

Этот пример полностью надуманный, но обратите внимание, что при вызове executeBoth(), db.find и doSomethingElse не нужно ждать завершения друг друга, и Node может выполнять оба кода примерно в одно и то же время, а код не блокируется.

Вот пример генератора, который попытается сделать то же самое:

function* executeBoth(){

  var results = yield db.find("value");
  console.log(results);

  var data = yield doSomethingElse("else");
  console.log(data);
}

Я не понимаю, как приведенный выше код позволяет первой функции не блокировать вторую. Из того, что я прочитал (источники ниже), кажется, что весь генератор приостанавливается, когда достигает ключевого слова yield. Это имеет смысл для строк кода, которые полагаются на значение, возвращаемое этим конкретным yield, но не означает ли это также, что db.find заблокирует выполнение doSomethingElse?

Кажется, что это можно решить, поместив каждое значение yielded и следующий код, который полагается на них, в их собственные отдельные генераторы, а затем вызывая эти генераторы из обычной функции. Тем не менее, если это самый эффективный способ создания неблокирующего кода, он будет способствовать чрезмерному использованию многих небольших функций-генераторов с потенциально повторяющимся специализированным кодом. Правильно ли я понимаю основную механику генераторов? Если да, то каков обходной путь для этого? Заранее спасибо.

первоначальный, второй источник, источник третий


person user3181113    schedule 01.07.2014    source источник
comment
Возможно, вы захотите взглянуть на Понимание потока кода с помощью yield/generators   -  person Bergi    schedule 01.07.2014


Ответы (1)


Вы правы, в вашем примере выполняется метод db.find, генератор ждет завершения этого действия, а затем возобновляет работу, пока не достигнет doSomethingElse.

Я собираюсь украсть следующие примеры из библиотеки-генератора co. (Вам не нужно его использовать, вы можете использовать простые генераторы, но мне нравится синтаксис, и примеры заставили их «щелкнуть» для меня)

Предположим, что в следующих примерах получение Google занимает 10 секунд, получение Yahoo — 6, а Cloudup — 5.

co(function *(){
  var a = yield get('http://google.com');
  var b = yield get('http://yahoo.com');
  var c = yield get('http://cloudup.com');
  console.log(a[0].statusCode);
  console.log(b[0].statusCode);
  console.log(c[0].statusCode);
})()

Это извлекает первый сайт, ждет, затем извлекает второй, ждет, затем извлекает третий. Это как твой пример. Это закончится через 10 + 6 + 5 = ~ 21 секунд.

co(function *(){
  var a = get('http://google.com');
  var b = get('http://yahoo.com');
  var c = get('http://cloudup.com');
  var res = yield [a, b, c];
  console.log(res);
})()

Это, однако, делает что-то другое: он начинает извлекать первое, второе и третье. Возвращаемые значения (a,b,c) представляют собой либо обещания, которые в конечном итоге сообщат о завершении, либо простые обратные вызовы.
Оператор yield будет ждать, пока не будут разрешены все три обещания/обратные вызовы. Неважно, в каком порядке. Это закончится через ~ 10 секунд.

person RickN    schedule 01.07.2014
comment
Да, получение массива промисов или переходников более эффективно, но это все еще блокирует. Что делать, если запрос на получение к cloudup.com выполняется за 4 секунды до выполнения запроса к yahoo.com? Затем запрос cloudup.com должен ждать записи в журнал до запроса yahoo.com, потому что этот yield приостанавливает все выполнение до тех пор, пока не будут получены все три значения. - person user3181113; 01.07.2014
comment
@ user3181113: Да, это план? Конечно, вы могли бы (по крайней мере, с промисами) также сделать var a = get('http://google.com'), b = get('http://yahoo.com'), c = get('http://cloudup.com'); console.log(yield a); console.log(yield b); console.log(yield c);, чтобы a можно было зарегистрировать, если оно прибудет раньше двух других. - person Bergi; 02.07.2014
comment
хорошее объяснение, спасибо - person mrcrgl; 04.08.2015