Использование javascript Symbol.asyncIterator с для ожидания цикла
Я пытаюсь понять Symbol.asyncIterator javascript и в ожидании. Я написал простой код, который выдает ошибку:
TypeError: undefined is not a function
на линии, которая пытается использовать for await (let x of a)
,
Я не мог понять причину этого.
let a = {}
function test() {
for(let i=0; i < 10; i++) {
if(i > 5) {
return Promise.resolve(`Greater than 5: (${i})`)
}else {
return Promise.resolve(`Less than 5: (${i})`)
}
}
}
a[Symbol.asyncIterator] = test;
async function main() {
for await (let x of a) { // LINE THAT THROWS AN ERROR
console.log(x)
}
}
main()
.then(r => console.log(r))
.catch(err => console.log(err))
Я создаю пустой объект a
и вставьте ключ Symbol.asyncIterator
на тот же объект и назначить ему функцию с именем test
который возвращает Promise
, Тогда я использую for await of
цикл для перебора всех значений, которые будет возвращать функция.
Что я делаю неправильно?
PS: я на Node версии 10.13.0
и на последней версии Chrome
3 ответа
Быть действительным asyncIterator
, ваш test
функция должна вернуть объект с next
метод, который возвращает обещание объекта результата с value
а также done
свойства. (Технически, value
необязательно, если его значение будет undefined
а также done
необязательно, если его значение будет false
, но...)
Вы можете сделать это несколькими способами:
- Полностью вручную (неудобно, особенно если вы хотите правильный прототип)
- Половина вручную (немного менее неловко, но все еще неловко, чтобы получить правильный прототип)
- Использование функции асинхронного генератора (самое простое)
Вы можете сделать это полностью вручную (это не попытка получить правильный прототип):
function test() {
let i = -1;
return {
next() {
++i;
if (i >= 10) {
return Promise.resolve({
value: undefined,
done: true
});
}
return Promise.resolve({
value: i > 5 ? `Greater than 5: (${i})` : `Less than 5: (${i})`,
done: false
});
}
};
}
let a = {
[Symbol.asyncIterator]: test
};
async function main() {
for await (let x of a) {
console.log(x)
}
}
main()
.then(r => console.log(r))
.catch(err => console.log(err))
Вы можете сделать это наполовину вручную, написав функцию, которая возвращает объект с async
next
метод (все еще не пытается получить правильный прототип):
function test() {
let i = -1;
return {
async next() {
++i;
if (i >= 10) {
return {
value: undefined,
done: true
};
}
return {
value: i > 5 ? `Greater than 5: (${i})` : `Less than 5: (${i})`,
done: false
};
}
};
}
let a = {
[Symbol.asyncIterator]: test
};
async function main() {
for await (let x of a) {
console.log(x)
}
}
main()
.then(r => console.log(r))
.catch(err => console.log(err))
Или вы можете просто использовать async
Функция генератора (самая простая и автоматически получает нужный прототип):
async function* test() {
for (let i = 0; i < 10; ++i) {
yield i > 5 ? `Greater than 5: (${i})` : `Less than 5: (${i})`;
}
}
let a = {
[Symbol.asyncIterator]: test
};
async function main() {
for await (let x of a) {
console.log(x)
}
}
main()
.then(r => console.log(r))
.catch(err => console.log(err))
О прототипах: все асинхронные итераторы, которые вы получаете из самой среды выполнения JavaScript, наследуют от прототипа, который обеспечивает основную функцию, гарантирующую, что итератор также является итеративным (имея Symbol.iterator
быть функцией, возвращающей this
). Для этого прототипа нет общедоступного идентификатора или свойства, вам нужно прыгнуть через обручи, чтобы получить его:
const asyncIteratorPrototype =
Object.getPrototypeOf(
Object.getPrototypeOf(
async function*(){}.prototype
)
);
Тогда вы будете использовать это в качестве прототипа объекта с next
метод, который вы возвращаете:
return Object.assign(Object.create(asyncIteratorPrototype), {
next() {
// ...
}
});
test
Функция не должна возвращать обещание, но итератор (объект с next()
), этот метод должен затем вернуть Promise (что делает его асинхронным итератором), и что Promise должен разрешить объект, содержащий value
и done
ключ:
function test() {
return {
next() {
return Promise.resolve({ value: "test", done: false });
}
};
}
Теперь, когда это работает, это еще не так полезно. Однако вы можете создать такое же поведение с помощью функции асинхронного генератора:
async function* test() {
await Promise.resolve();
yield "test";
}
Или в вашем случае:
async function* test() {
for(let i = 0; i < 10; i++) {
if(i > 5) {
await Promise.resolve();
yield `Greater than 5: (${i})`;
}else {
await Promise.resolve();
yield `Less than 5: (${i})`;
}
}
}
Вы должны сделать test
async
функция генератора вместо этого, и yield
вместо return
:
let a = {}
async function* test() {
for(let i=0; i < 10; i++) {
if(i > 5) {
yield Promise.resolve(`Greater than 5: (${i})`)
}else {
yield Promise.resolve(`Less than 5: (${i})`)
}
}
}
a[Symbol.asyncIterator] = test;
async function main() {
for await (let x of a) {
console.log(x)
}
}
main()
.then(r => console.log(r))
.catch(err => console.log(err))
Похоже, test
функция должна быть асинхронной, чтобы x
в for await
разворачивается, даже если test
не await
где угодно, иначе x
будет Обещание, которое разрешает значение, а не само значение.
yield
ИНГ Promise.resolve
внутри асинхронного генератора это странно, если только вы не хотите, чтобы результат был обещанием (что потребовало бы дополнительного await
внутри for await
цикл), это будет иметь больше смысла await
внутри async
генератор, а затем yield
результат.
const delay = ms => new Promise(res => setTimeout(res, ms));
let a = {}
async function* test() {
for(let i=0; i < 10; i++) {
await delay(500);
if(i > 5) {
yield `Greater than 5: (${i})`;
}else {
yield `Less than 5: (${i})`;
}
}
}
a[Symbol.asyncIterator] = test;
async function main() {
for await (let x of a) {
console.log(x)
}
}
main()
.then(r => console.log(r))
.catch(err => console.log(err))
Если вы не сделали test
генератор, test
придется вернуть итератор (объект с value
собственность и next
функция).