简体   繁体   English

使用javascript的Symbol.asyncIterator与等待循环

[英]Using javascript's Symbol.asyncIterator with for await of loop

I am trying to understand javascript's Symbol.asyncIterator and for await of . 我试图了解javascript的Symbol.asyncIterator等待 I wrote some simple code and it throws an error saying: 我写了一些简单的代码,并抛出一个错误:

    TypeError: undefined is not a function

on the line which tries to use for await (let x of a) . 在尝试for await (let x of a)

I could not understand the reason for it. 我不明白原因。

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

I create an empty object a and insert a key Symbol.asyncIterator on the same object and assign it a function named test that returns a Promise . 我创建一个空对象a并在同一对象上插入键Symbol.asyncIterator ,并将其分配给名为test的函数,该函数返回Promise Then I use for await of loop to iterate over all the values that the function would return. 然后,我使用for await of循环来迭代该函数将返回的所有值。

What am I doing incorrectly? 我做错了什么?

PS: I am on the Node version 10.13.0 and on the latest version of Chrome PS:我在Node版本10.13.0和最新版本的Chrome

To be a valid asyncIterator , your test function must return an object with a next method that returns a promise of a result object with value and done properties. 要成为有效的asyncIterator ,您的test函数必须使用next方法返回一个对象,该方法将返回带有valuedone属性的结果对象的承诺。 (Technically, value is optional if its value would be undefined and done is optional if its value would be false , but...) (从技术上讲, value是可选的,如果它的值将undefineddone是可选的,如果它的值是false ,但是......)

You can do that in a few ways: 您可以通过以下几种方式做到这一点:

  1. Completely manually (awkward, particularly if you want the right prototype) 完全手动(笨拙,特别是如果您想要合适的原型时)
  2. Half-manually (slightly less awkward, but still awkward to get the right prototype) 半手动(稍微不那么尴尬,但仍然很难获得正确的原型)
  3. Using an async generator function (simplest) 使用异步生成器函数(最简单)

You can do it completely manually (this doesn't try to get the right prototype): 您可以完全手动完成此操作(这不会尝试获得正确的原型):

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

You can do it half-manually writing a function that returns an object with an async next method (still doesn't try to get the right prototype): 您可以手动编写一个函数,该函数使用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)) 

Or you can just use an async generator function (easiest, and automatically gets the right prototype): 或者,您可以只使用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)) 


About prototypes: All async iterators you get from the JavaScript runtime itself inherit from a prototype that provides the very basic feature of ensuring the iterator is also iterable (by having Symbol.iterator be a function returning this ). 关于原型:从JavaScript运行时本身获取的所有异步迭代器都继承自原型,该原型提供了确保迭代器也可迭代的非常基本的功能(通过让Symbol.iterator作为返回this的函数)。 There's no publicly-available identifer or property for that prototype, you have to jump through hoops to get it: 该原型没有可公开获得的标识符或属性,您必须跳过所有步骤才能获得它:

const asyncIteratorPrototype =
    Object.getPrototypeOf(
        Object.getPrototypeOf(
            async function*(){}.prototype
        )
    );

Then you'd use that as the prototype of the object with the next method that you're returning: 然后,将其用作对象的原型,并返回next方法:

return Object.assign(Object.create(asyncIteratorPrototype), {
    next() {
        // ...
    }
});

The test function must not return a promise, but an Iterator (an object with a next() ) method, that method then has to return a Promise (which makes it an async iterator) and that Promise has to resolve to an object containing a value and a done key: test函数一定不能返回promise,而是一个迭代器(带有next()的对象)方法,然后该方法必须返回一个Promise(使其成为异步迭代器),并且Promise必须解析为一个包含valuedone密钥:

function test() {
   return {
     next() {
       return Promise.resolve({ value: "test", done: false });
     }
   };
}

Now while that works, it is not that useful yet. 现在,尽管可行,但还没那么有用。 You could however create the same behaviour with an async generator function: 但是,您可以使用异步生成器函数创建相同的行为:

  async function* test() {
    await Promise.resolve();
    yield "test";
  }

Or in your case: 或您的情况:

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})`;
    }
  }
}

You should make test an async generator function instead, and yield instead of return : 你应该让testasync 发电机的功能 ,而是和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)) 

It looks like the test function needs to be async so that the x in the for await gets unwrapped, even though test doesn't await anywhere, otherwise the x will be a Promise that resolves to the value, not the value itself. 看起来test函数需要异步,以便for await中的x会被解包,即使test不在任何地方都await ,否则x将是一个解析为值而不是值本身的Promise。

yield ing Promise.resolve inside an async generator is odd, though - unless you want the result to be a Promise (which would require an extra await inside the for await loop), it'll make more sense to await inside the async generator, and then yield the result. 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)) 

If you didn't make test a generator, test would have to return an iterator (an object with a value property and a next function). 如果您没有使test成为生成器,则test必须返回一个迭代器 (具有value属性和next函数的对象)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM