Поиск…


Вступление

Функции генератора (определенные ключевым словом function* ) выполняются как сопрограммы, генерируя ряд значений по мере их запроса через итератор.

Синтаксис

  • function * name (parameters) {значение доходности; возвращаемое значение}
  • generator = имя (аргументы)
  • {значение, сделано} = generator.next (значение)
  • {значение, сделано} = generator.return (значение)
  • generator.throw (ошибка)

замечания

Функции генератора - это функция, представленная как часть спецификации ES 2015 и недоступная во всех браузерах. Они также полностью поддерживаются в Node.js с v6.0 . Подробный список совместимости браузеров см. В документации MDN и узле, см. Веб- сайт node.green .

Функции генератора

Функция генератора создается с помощью декларации function* . Когда он вызывается, его тело не выполняется сразу. Вместо этого он возвращает объект-генератор , который может использоваться для «выполнения» выполнения функции.

Выражение yield внутри тела функции определяет точку, в которой выполнение может приостанавливаться и возобновляться.

function* nums() {
    console.log('starting');  // A
    yield 1;                  // B
    console.log('yielded 1'); // C
    yield 2;                  // D
    console.log('yielded 2'); // E
    yield 3;                  // F
    console.log('yielded 3'); // G
}
var generator = nums(); // Returns the iterator. No code in nums is executed

generator.next();  // Executes lines A,B returning { value: 1, done: false }
// console: "starting"
generator.next();  // Executes lines C,D returning { value: 2, done: false }
// console: "yielded 1"
generator.next();  // Executes lines E,F returning { value: 3, done: false }
// console: "yielded 2"
generator.next();  // Executes line G returning { value: undefined, done: true }
// console: "yielded 3"

Ранний выход итерации

generator = nums();
generator.next(); // Executes lines A,B returning { value: 1, done: false }
generator.next(); // Executes lines C,D returning { value: 2, done: false }
generator.return(3); // no code is executed returns { value: 3, done: true }
// any further calls will return done = true 
generator.next(); // no code executed returns { value: undefined, done: true }

Выброс ошибки в функцию генератора

function* nums() {
    try {
        yield 1;                  // A
        yield 2;                  // B
        yield 3;                  // C
    } catch (e) {
        console.log(e.message);    // D
    }
}

var generator = nums();

generator.next();  // Executes line A returning { value: 1, done: false }
generator.next();  // Executes line B returning { value: 2, done: false }
generator.throw(new Error("Error!!"));  // Executes line D returning { value: undefined, done: true}
// console: "Error!!"
generator.next();  // no code executed. returns { value: undefined, done: true }

итерация

Генератор истребитель . Он может быть закодирован с помощью инструкции for...of и использоваться в других конструкциях, которые зависят от протокола итерации.

function* range(n) {
    for (let i = 0; i < n; ++i) {
        yield i;
    }
}

// looping
for (let n of range(10)) {
    // n takes on the values 0, 1, ... 9
}

// spread operator
let nums = [...range(3)];  // [0, 1, 2]
let max = Math.max(...range(100));  // 99

Вот еще один пример использования генератора для пользовательского итеративного объекта в ES6. Здесь используется function * анонимного генератора.

let user = {
  name: "sam", totalReplies: 17, isBlocked: false
};

user[Symbol.iterator] = function *(){

  let properties = Object.keys(this);
  let count = 0;
  let isDone = false;

  for(let p of properties){
    yield this[p];
  }
};

for(let p of user){
  console.log( p );
} 

Отправка значений генератору

Можно отправить значение генератору, передав его next() методу.

function* summer() {
    let sum = 0, value;
    while (true) {
        // receive sent value
        value = yield;
        if (value === null) break;

        // aggregate values
        sum += value;
    }
    return sum;
}
let generator = summer();

// proceed until the first "yield" expression, ignoring the "value" argument
generator.next();

// from this point on, the generator aggregates values until we send "null"
generator.next(1);
generator.next(10);
generator.next(100);

// close the generator and collect the result
let sum = generator.next(null).value;  // 111

Передача другому генератору

Из функции генератора управление может быть делегировано другой функции генератора с использованием yield* .

function* g1() {
  yield 2;
  yield 3;
  yield 4;
}

function* g2() {
  yield 1;
  yield* g1();
  yield 5;
}

var it = g2();

console.log(it.next()); // 1
console.log(it.next()); // 2
console.log(it.next()); // 3
console.log(it.next()); // 4
console.log(it.next()); // 5
console.log(it.next()); // undefined

Интерфейс Iterator-Observer

Генератор представляет собой комбинацию из двух вещей - Iterator и Observer .

Итератор

Итератором является то, что при вызове возвращает iterable . iterable - это то, что вы можете повторить. Начиная с ES6 / ES2015, все коллекции (Array, Map, Set, WeakMap, WeakSet) соответствуют контракту Iterable.

Генератор (итератор) является производителем. В итерации потребитель PULL от производителя.

Пример:

function *gen() { yield 5; yield 6; }
let a = gen();

Всякий раз , когда вы звоните a.next() , вы существенно pull -ную значение из итератора и pause исполнение на yield . При следующем вызове a.next() выполнение возобновляется из ранее приостановленного состояния.

наблюдатель

Генератор также является наблюдателем, с помощью которого вы можете отправить некоторые значения обратно в генератор.

function *gen() {
  document.write('<br>observer:', yield 1);
}
var a = gen();
var i = a.next();
while(!i.done) {
  document.write('<br>iterator:', i.value);
  i = a.next(100);
}

Здесь вы можете видеть, что yield 1 используется как выражение, которое оценивается до некоторого значения. Значение, которое оно оценивает, - это значение, отправленное в качестве аргумента для a.next функции a.next .

Таким образом, впервые i.value будет первым значением, полученным ( 1 ), и при продолжении итерации к следующему состоянию мы отправим значение обратно генератору, используя a.next(100) .

Выполнение асинхронизации с генераторами

Генераторы широко используются с функцией spawn (from taskJS или co), где функция принимает генератор и позволяет нам писать асинхронный код синхронно. Это НЕ означает, что асинхронный код преобразуется в синхронный код / ​​выполняется синхронно. Это означает, что мы можем писать код, похожий на sync но внутри он все еще async .

Синхронизация - БЛОКИРОВКА; Async ОЖИДАЕТ. Написание кода, который блокирует, легко. Когда PULLing, значение появляется в позиции назначения. Когда PUSHing, значение появляется в позиции аргумента обратного вызова.

Когда вы используете итераторы, вы PULL значение от производителя. Когда вы используете обратные вызовы, производитель PUSH присваивает значение позиции аргумента обратного вызова.

var i = a.next() // PULL
dosomething(..., v => {...}) // PUSH

Здесь вы вытаскиваете значение из a.next() а во втором, v => {...} - это обратный вызов, а значение PUSH ed в позицию аргумента v функции обратного вызова.

Используя этот механизм pull-push, мы можем написать асинхронное программирование, как это,

let delay = t => new Promise(r => setTimeout(r, t));
spawn(function*() {
  // wait for 100 ms and send 1
  let x = yield delay(100).then(() => 1);
  console.log(x); // 1

   // wait for 100 ms and send 2
  let y = yield delay(100).then(() => 2);
  console.log(y); // 2
});

Итак, глядя на приведенный выше код, мы пишем асинхронный код, который выглядит как blocking (операторы доходности ждут 100 мс, а затем продолжают выполнение), но это на самом деле waiting . Свойство pause и resume генератора позволяет нам сделать этот потрясающий трюк.

Как это работает ?

Функция spawn использует yield promise чтобы PULL состояние обещания от генератора, ждет, пока обещание будет разрешено, и ОТПУСКАЕТ полученное значение обратно генератору, чтобы он мог его использовать.

Используйте его сейчас

Итак, с генераторами и функцией spawn вы можете очистить весь ваш асинхронный код в NodeJS, чтобы выглядеть и чувствовать себя синхронным. Это облегчит отладку. Также код будет выглядеть аккуратно.

Эта функция подходит к будущим версиям JavaScript - async...await . Но вы можете использовать их сегодня в ES2015 / ES6, используя функцию spawn, определенную в библиотеках - taskjs, co или bluebird

Асинхронный поток с генераторами

Генераторы - это функции, которые могут приостановить и возобновить выполнение. Это позволяет эмулировать асинхронные функции с использованием внешних библиотек, в основном q или co. В основном это позволяет записывать функции, которые ждут результатов async для продолжения:

function someAsyncResult() {
    return Promise.resolve('newValue')
}

q.spawn(function * () {
    var result = yield someAsyncResult()
    console.log(result) // 'newValue'
})

Это позволяет написать асинхронный код, как если бы он был синхронным. Кроме того, попробуйте и поймайте работу над несколькими асинхронными блоками. Если обещание отклонено, ошибка будет улавливаться следующим уловом:

function asyncError() {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            reject(new Error('Something went wrong'))
        }, 100)
    })
}

q.spawn(function * () {
    try {
        var result = yield asyncError()
    } catch (e) {
        console.error(e) // Something went wrong
    }
})

Использование co будет работать точно так же, но с co(function * (){...}) вместо q.spawn



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow