Comments 22
Судя по всему, поддержку недавно добавили в V8. Не использую Chrome, так что не могу проверить.
Вы не могли бы ещё раз рассказать, почему альтернативный подход с IteratorResult не годится? Немного непонятно получилось в статье.
Ну вот разгружаете вы вагоны с дерьмомзерном. Подставляете спину, туда прилетает мешок, вы его быстренько-быстренько в закрома отсыпаете там, конечно, и за следующим, пока мешки летают. Но вот рабочий день закончился, вагон опустел, но хотя последний рабочий, уходя, похлопал вас по спине, аки ломовую лошадь, вы разогнуться уже не можете, так и стоите в интересной позе тылом к рельсам. А ведь могли бы домой пойти, если бы на последнем мешке была этикетка с надписью "все, баста!"...
Всё равно не понял)) Если без метафор, то в чём загвоздка?
Это сделано для тех случаев, когда мы изнутри синхронного генератора не можем определить есть ли следующее значение или нет. Например нужно сходить в некую удалённую очередь по сети и забрать следующее значение, если очередь опустела, то выйти из цикла.
Судя по коду, мы внутри генератора делаем await (дальнейший код исполнится когда "фоновый" Promise заресолвится, если я правильно понимаю), и потом проверяем на переменную на null как условие, что данных больше нет. Так может это условие и использовать как знак того, что "баста"?
А снаружи-то как вовремя узнать что больше элементов не будет?
Чему будет равно свойство done итератора во время выполнения оператора await в генераторе?
А снаружи-то как вовремя узнать что больше элементов не будет?
Снаружи "вовремя" будет тогда, когда зарезолвится промис, т.к. пока это не произойдёт ваш цикл не сможет пойти дальше. А когда промис резолвится, то он возвращает состояние итератора, где указано завершился итератор или нет.
Чему будет равно свойство done итератора во время выполнения оператора await в генераторе?
Оно будет равно false
. Станет равным true
лишь при выходе из функции-генератора явно (по return
) или неявно (кончилось тело функции).
Вот смотрите, проверили мы done. Оно равно false. Мы получили очередной промиз и начали его ждать.
А следующего элемента-то и нет! Как теперь закончить ожидание?
PS блин, да вы ниже сами все расписали с кодом! Зачем тут чушь пишите?
Когда вызывается return
или мы выходим из генератора, то всё равно возвращается Promise
, содержащий IteratorResult
. Вот пример с кодом:
async function* values () {
yield 1
yield 2
}
function handle () {
const iter = values()
console.log(iter.next()) // Promise { value: 1, done: false }
console.log(iter.next()) // Promise { value: 2, done: false }
// следующего элемента нет
console.log(iter.next()) // Promise { value: undefined, done: true }
}
handle()
Т.е. последний промис разрешается сразу и возвращает done = true
. Вы можете запустить этот код и проверить самостоятельно.
Зачем тут чушь пишите?
В каком месте я написал чушь?
Это вы сейчас написали как Promise<IteratorResult>
работает.
А я отвечал на вот этот вопрос:
Вы не могли бы ещё раз рассказать, почему альтернативный подход с IteratorResult не годится? Немного непонятно получилось в статье.
Альтернативный — это тот, при котором метод next() возвращает IteratorResult<Promise>
.
for(const taskPromise of queue) {
const task = await taskPromise;
task ? resolve(task) : break;
}
Просто итератор будет бесконечный и проверку на выход прийдется делать руками каждый раз. В случае же асинхронного итератора у вас проверка будет спрятана в сам итератор и он не будет бесконечным.
Давайте избавимся от for-await-of
и посмотрим как это можно обработать вручную. Допустим, у нас есть удалённая очередь queue
с несколькими элементами.
Пример для IteratorResult<Promise>
:
const queue = ... // queue это итератор
while (true) {
const { value, done } = queue.next()
// done всегда будет false, т.к. из генератора синхронно(!) мы не можем узнать закончилась ли очередь
const result = await value // здесь мы дожидаемся разрешения value
if (result === null) { break } // вот здесь нужно проверить, что нам вернулось пустое значение. Но дело в том, что null может быть вполне валидным значением, а не индикатором пустой очереди
// обрабатываем result; следующая итерация
}
Как видите, этот подход имеет недостаток — у нас нет четкого понимания, что очередь пуста. Мы не можем трактовать null как конец, если только не приняли некое соглашение, что null — это всегда конец очереди.
В случае с Promise<IteratorResult>
всё несколько иначе:
const queue = ...
while (true) {
const { value, done } = await queue.next() // здесь у нас есть Promise, который возвращает текущее состояние итератора
if (done) { break } // и есть четкое понимание когда стоит прекратить цикл
// обрабатываем value; следующая итерация
}
Т.е. при подходе Promise<IteratorResult>
у нас есть возможность без всяких соглашений четко дать понять, что очередь пуста, можно выходить из цикла. queue
, например, может при каждом вызове next()
помимо получения элемента спрашивать у очереди сколько элементов осталось и при значении 0
вернуть done = true
, чтобы прервать цикл и не создать последующих запросов.
Спасибо, почти понятно. А если сильно извратиться — можно ли чисто теоретически снаружи повлиять на наш генератор, основываясь на информации, которую вернёт Promise? Например, опять же, передавать помимо значения некий флаг конца в поле объекта.
Хотя понимаю, что это сильный костыль.
А если сильно извратиться — можно ли чисто теоретически снаружи повлиять на наш генератор
Да, вы можете в next()
передать какое-нибудь значение:
function* generator () {
let value = yield 'Hi!'
console.log('Hello %s!', value)
}
const iterator = generator()
console.log(iterator.next().value) // выведет "Hi!"
iterator.next('World') // выведет "Hello World!"
Таким образом можно передать генератору что угодно.
А можно ли будет применять функции map reduce filter и пр. над асинхронными итераторами? Получилась бы хорошая замена observable.
Нет. Но наверняка там появятся асинхронные версии этих функций.
Сейчас и для обычных итераторов их нельзя применить. Для этого сначала нужно преобразовать в массив (с помощью Array.from
или spread оператора [ ...iterable ]
), а потом над ним уже совершать операции.
В качестве эксперимента я пишу библиотеку, которая добавляет эти методы прямо к итератору (изменяет его прототип, как делает SugarJS), но она далека от завершения, к тому же есть множество более качественных альтернатив: Wu, Lazy.JS и т.д.
function functor(Target) {
if(!Reflect.has(Target, Symbol.asycIterator)) throw new Error(`${Target} should be async iterable`);
if(!Reflect.defineProperty(Target.prototype, 'map', {
value: async function(transform) {
const res = [];
for await(const el of this) {
res.push(transform(el));
}
return res;
}
}) throw new Error(`${Target} already has a map method`);
return Target;
}
@functor // или после объявления класса functor(Queue) если не хочется включать бабель для декоратора
class Queue {...}
В данном примере map просто асинхронно выгребет коллекцию, применит к ней заданную трансформацию и вернет промис с результатом, но можно поведение усложнять и генерировать события на каждый приход элемента.
Лучше уж прототип заменить, чем патчить каждый объект...
А, так вы там класс патчите, а не метод. Да, тогда все правильно.
Хотя можно было бы обойтись просто базовым классом в таком случае.
Новинки JavaScript: Асинхронные итераторы