使用 javascript 的 Symbol.asyncIterator 和 for await of loop

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

我正在尝试理解 javascript 的 Symbol.asyncIterator and for await of。我写了一些简单的代码,它抛出一个错误:

    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 的函数,该函数 return 是一个 Promise.然后我使用 for await of 循环遍历函数将 return.

的所有值

我做错了什么?

PS:我在 Node 版本 10.13.0 和最新版本 Chrome

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

yielding Promise.resolve inside an async generator is odd, though - unless you want 结果是 Promise(这需要额外的awaitfor await 循环内),在 async 生成器内 await 更有意义,然后 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 将不得不 return 一个 iterator(具有 value 属性 和一个 next 函数)。

test 函数不能 return 一个 promise,而是一个迭代器(一个带有 next() 的对象)方法,然后该方法必须 return 一个 Promise (这使它成为一个异步迭代器)并且 Promise 必须解析为一个包含 valuedone 键的对象:

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

要成为有效的 asyncIterator,您的 test 函数必须 return 具有 next 方法的对象 return 是结果对象的承诺具有 valuedone 属性。 (从技术上讲,如果 value 的值为 undefined,则 value 是可选的;如果其值为 false,则 done 是可选的,但是...)

您可以通过几种方式做到这一点:

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

您可以半手动编写一个函数,该函数 return 是一个具有 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 运行时本身获得的所有异步迭代器都继承自原型,该原型提供确保迭代器也是 iterable 的非常基本的功能(通过让 Symbol.iterator 成为函数 returning this)。该原型没有公开可用的标识符或 属性,您必须跳过箍才能获得它:

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

然后您将使用它作为对象的原型以及您正在 returning:

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