6

我试图了解 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

4

3 回答 3

7

要成为有效的asyncIterator,您的test函数必须返回一个对象,该对象的next方法返回具有valuedone属性的结果对象的承诺。(从技术上讲,value如果它的值是可选的,如果它的值undefineddone可选的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() {
        // ...
    }
});
于 2019-04-05T08:47:48.040 回答
2

test函数不能返回一个promise,而是一个Iterator(一个带有a的对象next())方法,然后该方法必须返回一个Promise(这使它成为一个异步迭代器)并且Promise必须解析为一个包含avalue和一个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})`;
    }
  }
}
于 2019-04-05T08:35:25.287 回答
0

您应该创建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函数需要是异步的,以便xinfor await被解包,即使在任何地方test都没有await,否则x将是一个解析为值的 Promise,而不是值本身。

yield但是,在异步生成器Promise.resolve中进行操作很奇怪 - 除非您希望await结果是 Promise(这将需要在循环中额外添加),否则在生成器内部,然后是结果for await会更有意义。awaitasyncyield

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函数的对象)。

于 2019-04-05T08:29:54.657 回答