Генераторы в JavaScript

Aug 4, 2018

Генераторы - это специальный тип функции, который работает как фабрика итераторов. Генераторы аналогичны функциям, но их выполнение может быть приостановлено (с помощью оператора yield) и возобновлено позже.

Генераторы полезно использовать совместно с итераторами.

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

Итератор

В JavaScript итератор - это объект, который предоставляет метод next(), возвращающий следующий элемент последовательности. Этот метод возвращает объект с двумя свойствами: done и value.

Вы можете использовать for(var value of iterator) {}, чтобы перебрать значения в итераторе.

Вызов функции-генератора (function*() {}) возвратит итератор.

Более подробно на Итераторы в JavaScript

Функция-генератор объявляется при помощи * после ключевого слова function:

function* doGenerator() {
    //...
}

yield

Выполнение doGenerator можно приостановить (с помощью оператора yield) и вернуть вызвавшему объекту некоторое значение.

function* doGenerator() {
	yield 'Hello';
	console.log('after Hello');
}

let gen = doGenerator();

console.log(gen.next()) // { value: "Hello", done: false }
console.log(gen.next()) // after Hello
                        // { value: undefined, done: true }

next

next() - метод объекта генератора, который запускает/возобновляет генератор и возвращает объект вида:

{
    value: <значение>
    done:  <true, если завершено>
}
function* newDoGenerator() {
	yield 'Hello';
	yield 'Hello1';
	return 'By'

}

let newGen = newDoGenerator();
console.log(newGen.next()) //{ value: "Hello", done: false }
console.log(newGen.next()) //{ value: "Hello1", done: false }
console.log(newGen.next()) //{ value: "By", done: true }

Передача значений обратно в генератор

Чтобы передать значение обратно в генератор достаточно передать значение методу next и это значение станет возвращаемым значением оператора yield.

function* passValGenerator() {
    const name = yield null;
    console.log('Hello ' + name);
}

const twoWay = passValGenerator();
twoWay.next();
twoWay.next('Petya'); // Hello Petya

Используем генераторы с асинхронным кодом

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

Функция принимает генератор, создает его экземпляр и запускает его, передавая ему в качестве аргумента заранее определенный cb. cb переданный в асинхронную функцию будет возобновлять работу генератору после выполнения асинхронной операции (обратите внимание - все асинхронные функции работают в парадигме Node.js:

Соглашения о callback в Node.js:
1. Обратные вызовы передаются последними: fs.readFile(filename, [options], callback)
2. Ошибки всегда передаются первыми (если операция прошла успешно в первом аргументе передается null или undefined) в callback; ошибка должна иметь тип Error
3. Для распространения ошибки применяется оператор return: return callback(error);

cb вызывает generator.throw(err) при появлении ошибки; иначе продолжает работу генератора, передавая дальше результаты, полученные от cb.

function asyncFlow(generatorFunction) {

    function cb(err) {
        if (err) {
            return generator.throw(err);
        }
        const results = [].slice.call(arguments, 1);
		console.log('results: ', results);

// Чтобы передать значение обратно в генератор нужно передать значение методу next и это значение станет возвращаемым значением оператора yield
        generator.next(results.length > 1 ? results : results[0]);
    }

    const generator = generatorFunction(cb);
    generator.next();

}

const delayedDivision = (div, divis, cb) => {
    setTimeout(() => {
        if (typeof div !== 'number' || typeof divis !== 'number' || divis === 0) {
            cb(new Error('Invalid'));
        }

        cb(null, div / divis);
    }, 1000);
};

asyncFlow(function*(cb) {
    const on = yield delayedDivision(20, 10, cb);
    const tw = yield delayedDivision(20, 10, cb);
    console.log('on + tw: ', on + tw); // on + tw:  4
});

Пример на jsfiddle.net

http://jsfiddle.net/dnzl/vwfyrc3L/embed/js/dark/
Добавить комментарий