Использование 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, но...)

Вы можете сделать это несколькими способами:

  1. Полностью вручную (неудобно, особенно если вы хотите правильный прототип)
  2. Половина вручную (немного менее неловко, но все еще неловко, чтобы получить правильный прототип)
  3. Использование функции асинхронного генератора (самое простое)

Вы можете сделать это полностью вручную (это не попытка получить правильный прототип):

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))

Вы можете сделать это наполовину вручную, написав функцию, которая возвращает объект с asyncnext метод (все еще не пытается получить правильный прототип):

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 функция).

Другие вопросы по тегам