Использование javascript Symbol.asyncIterator с для ожидания цикла - PullRequest
1 голос
/ 05 апреля 2019

Я пытаюсь понять javascript Symbol.asyncIterator и в ожидании .Я написал простой код, и он выдает ошибку:

    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 ]

3 голосов
/ 05 апреля 2019

Чтобы быть действительным 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))

Вы можете сделать это наполовину вручную, написав функцию, которая возвращает объект с помощью метода 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() {
        // ...
    }
});
2 голосов
/ 05 апреля 2019

Функция test не должна возвращать обещание, но метод Iterator (объект с 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})`;
    }
  }
}
0 голосов
/ 05 апреля 2019

Вы должны сделать 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 ing 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).

...