Поиск…
Вступление
Функции генератора (определенные ключевым словом 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