Я изучал генераторы и итераторы javascript и задавался вопросом, есть ли способ написать функцию генератора для возврата значения в текущей позиции --- конечно, без необходимости вызывать next() или запоминать возвращаемое значение из последнего вызова next() .
Точнее, моя неудачная попытка:
function* iterable(arr) {
this.index = 0;
this.arr = arr;
while(this.index < this.arr.length) {
yield this.arr[this.index++];
}
}
iterable.prototype.current = function () {
return this.arr[this.index];
}
const i = iterable([0, 1, 2]);
console.info(i.current()); // TypeError: Cannot read property 'undefined' of undefined
Желаемую функциональность можно реализовать с помощью такого класса (я знаю, что возвращаемые значения от итератора будут такими объектами, как { value: 1, done: false }):
class iterableClass {
constructor(arr) {
this.index = 0;
this.arr = arr;
}
get(i) {
return this.index < arr.length ? this.arr[this.index] : false;
}
next() {
const val = this.get(this.index);
this.index++;
return val;
}
current() {
return this.get(this.index);
}
}
const i = iterableClass([0, 1, 2]);
console.info(i.current()); // 0
Хотя я мог просто работать с классом (или даже с простой старой функцией), мне было интересно, можно ли это сделать с помощью генератора / итератора или, может быть, есть еще лучший вариант.



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Почему бы не использовать функцию из Итераторы и генераторы MDN, где только возвращаемая часть заменяется значением вместо объекта со свойством value и done
function makeIterator(array) {
var nextIndex = 0,
lastValue;
return {
next: function() {
return lastValue = nextIndex < array.length ? array[nextIndex++] : undefined;
},
last: function () {
return lastValue;
}
};
}
var it = makeIterator(['yo', 'ya']);
console.info(it.next());
console.info(it.next());
console.info(it.last());
console.info(it.next());возможно, но с большими накладными расходами.
Кажется, есть несколько интерпретаций этого вопроса. Насколько я понимаю, вам нужен итератор, который предоставляет способ доступа к самому недавно полученному значению, как показано последней строкой в вашем последнем блоке кода:
console.info(i.current()); // 0
Это не является частью интерфейса итератора и не обеспечивается функциями генератора. Вы можете предоставить оболочку итератора, которая это делает, а затем использовать ее в генераторе из функции генератора (хотя вам не нужен генератор для того, что вы делаете, стандартный итератор массива делает это), см. Комментарии:
// Get the Iterator prototype, which has no global name
const itPrototype = Object.getPrototypeOf(
Object.getPrototypeOf([][Symbol.iterator]())
);
function currentWrapper(source) {
// Allow source to be an iterable or an iterator
if (Symbol.iterator in source) {
source = source[Symbol.iterator]();
}
// Create our wrapper iterator
const it = Object.create(itPrototype);
// Remember the last value we saw from `next`
let current = null;
// The iterator method
it.next = () => {
return current = source.next();
};
// Our additional methods
it.current = () => current && current.value;
it.currentResult = () => ({...current});
return it;
}
Это имеет то преимущество, что оно универсально и многократно используется, а не привязано к конкретному итерируемому объекту.
Живой пример:
// Get the Iterator prototype, which has no global name
const itPrototype = Object.getPrototypeOf(
Object.getPrototypeOf([][Symbol.iterator]())
);
function currentWrapper(source) {
// Allow source to be an iterable or an iterator
if (Symbol.iterator in source) {
source = source[Symbol.iterator]();
}
// Create our wrapper iterator
const it = Object.create(itPrototype);
// Remember the last value we saw from `next`
let current = null;
// The iterator method
it.next = () => {
return current = source.next();
};
// Our additional methods
it.current = () => current && current.value;
it.currentResult = () => ({...current});
return it;
}
// Something to iterate over
const a = [1, 2, 3];
// Example use #1: Using `current`
const it = currentWrapper(a[Symbol.iterator]());
console.info("current", it.current()); // undefined
console.info("next", it.next()); // {value: 1, done: false}
console.info("current", it.current()); // 1
console.info("currentResult", it.currentResult()); // {value: 1, done: false}
// Example use #2: Just normal use of an iterator
for (const value of currentWrapper(a)) {
console.info(value);
}.as-console-wrapper {
max-height: 100% !important;
}Я сосредоточился на бите current, а не на бите index, потому что я думаю об итерациях как о потоках, а не массивах, но я полагаю, что было бы достаточно просто добавить index. Немного сложная часть - когда итератор закончил работу, увеличиваете ли вы индекс при вызове next или нет? Ниже нет:
// Get the Iterator prototype, which has no global name
const itPrototype = Object.getPrototypeOf(
Object.getPrototypeOf([][Symbol.iterator]())
);
function currentWrapper(source) {
// Allow source to be an iterable or an iterator
if (Symbol.iterator in source) {
source = source[Symbol.iterator]();
}
// Create our wrapper iterator
const it = Object.create(itPrototype);
// Remember the last value we saw from `next` and the current "index"
let current = null;
let index = -1;
// The iterator method
it.next = () => {
// Don't increase the index if "done" (tricky bit)
if (!current || !current.done) {
++index;
}
return current = source.next();
};
// Our additional methods
it.current = () => current && current.value;
it.currentResult = () => ({...current});
it.currentIndex = () => index;
return it;
}
// Something to iterate over
const a = [1, 2, 3];
// Example use #1: Using `current`
const it = currentWrapper(a[Symbol.iterator]());
console.info("current", it.current()); // undefined
console.info("next", it.next()); // {value: 1, done: false}
console.info("current", it.current()); // 1
console.info("currentResult", it.currentResult()); // {value: 1, done: false}
console.info("currentIndex", it.currentIndex()); // 0
console.info("next", it.next()); // {value: 2, done: false}
console.info("current", it.current()); // 2
console.info("currentResult", it.currentResult()); // {value: 2, done: false}
console.info("currentIndex", it.currentIndex()); // 1
// Example use #2: Just normal use of an iterator
for (const value of currentWrapper(a)) {
console.info(value);
}.as-console-wrapper {
max-height: 100% !important;
}Проблема с вашей функцией генератора заключается в том, что а) она не запускается, когда вы ее вызываете, она просто создает генератор (this.arr и this.index не будут инициализированы до первого вызова next()) и б) нет возможности доступ к объекту-генератору изнутри функции, как вы пробовали с this.
Вместо этого вы бы хотели
function iterable(arr) {
const gen = Object.assign(function* () {
while (gen.index < gen.arr.length) {
yield gen.arr[gen.index++];
}
}(), {
arr,
index: 0,
current() {
return gen.arr[gen.index];
},
});
return gen;
}
В качестве альтернативы вместо использования синтаксиса генератора вы также можете напрямую реализовать интерфейс Iterator:
function iterable(arr) {
return {
arr,
index: 0,
current() { return this.arr[this.index]; },
next() {
const done = !(this.index < this.arr.length);
return { done, value: done ? undefined : this.arr[this.index++] };
},
[Symbol.iterator]() { return this; },
};
}
(который, конечно, можно было бы записать и как class)
Я не совсем уверен, как использовать ваши функции, но const x = iterable([1, 2, 3]); x.next(); генерирует TypeError: x.next() is not a function для первого примера. Второй пример возвращает { done: true, value: undefined } при первом вызове .next().
@phippu Ой, спасибо за тестирование. Вы правы, первое не имело смысла, я выбрал лучшее решение.
Я думаю, что цель состояла в том, чтобы иметь итератор с дополнительной функцией (доступ к текущему [последнему полученному]) значению.