Ein tiefer Einblick in asynchrone Generatoren und asynchrone Iteration in Node.js

青灯夜游
Freigeben: 2020-09-19 10:20:48
nach vorne
2064 Leute haben es durchsucht

Ein tiefer Einblick in asynchrone Generatoren und asynchrone Iteration in Node.js

Das Erscheinen von Generatorfunktionen in JavaScript geht auf die Einführung von async/await zurück, was bedeutet, dass beim Erstellen eines asynchronen Generators (der immerPromisezurückgibt und mitawait generiert werden kann) Code> Gerät), führt es auch viele Dinge ein, die Aufmerksamkeit erfordern.
Promise且可以await的生成器)的同时,还引入了许多需要注意的事项。

今天,我们将研究异步生成器及其近亲——异步迭代。

注意:尽管这些概念应该适用于所有遵循现代规范的 javascript,但本文中的所有代码都是针对 Node.js 10、12和 14 版开发和测试的。

视频教程推荐:node js教程

异步生成器函数

看一下这个小程序:

// File: main.js const createGenerator = function*(){ yield 'a' yield 'b' yield 'c' } const main = () => { const generator = createGenerator() for (const item of generator) { console.log(item) } } main()
Nach dem Login kopieren

这段代码定义了一个生成器函数,用该函数创建了一个生成器对象,然后用for ... of循环遍历该生成器对象。相当标准的东西——尽管你绝不会在实际工作中用生成器来处理如此琐碎的事情。如果你不熟悉生成器和for ... of循环,请看《Javascript 生成器》 和 《ES6 的循环和可迭代对象的》 这两篇文章。在使用异步生成器之前,你需要对生成器和for ... of循环有扎实的了解。

假设我们要在生成器函数中使用await,只要需要用async关键字声明函数,Node.js 就支持这个功能。如果你不熟悉异步函数,那么请看 《在现代 JavaScript 中编写异步任务》一文。

下面修改程序并在生成器中使用await

// File: main.js const createGenerator = async function*(){ yield await new Promise((r) => r('a')) yield 'b' yield 'c' } const main = () => { const generator = createGenerator() for (const item of generator) { console.log(item) } } main()
Nach dem Login kopieren

同样在实际工作中,你也不会这样做——你可能会await来自第三方 API 或库的函数。为了能让大家轻松掌握,我们的例子尽量保持简单。

如果尝试运行上述程序,则会遇到问题:

$ node main.js /Users/alanstorm/Desktop/main.js:9 for (const item of generator) { ^ TypeError: generator is not iterable
Nach dem Login kopieren

JavaScript 告诉我们这个生成器是“不可迭代的”。乍一看,似乎使生成器函数异步也意味着它生成的生成器是不可迭代的。这有点令人困惑,因为生成器的目的是生成“以编程方式”可迭代的对象。

接下来搞清楚到底发生了什么。

检查生成器

如果你看了Javascript 生成器这篇文章 ,那么就应该知道,如果对象定义了Symbol.iterator方法,并且该方法返回,则它在 javascript 中是一个实现了迭代器协议的可迭代对象。当对象具有next方法时,该对象将实现迭代器协议,并且该next方法返回带有value属性,done属性之一或同时带有valuedone属性的对象。

如果用下面这段代码比较异步生成器函数与常规生成器函数返回的生成器对象:

// File: test-program.js const createGenerator = function*(){ yield 'a' yield 'b' yield 'c' } const createAsyncGenerator = async function*(){ yield await new Promise((r) => r('a')) yield 'b' yield 'c' } const main = () => { const generator = createGenerator() const asyncGenerator = createAsyncGenerator() console.log('generator:',generator[Symbol.iterator]) console.log('asyncGenerator',asyncGenerator[Symbol.iterator]) } main()
Nach dem Login kopieren

则会看到,前者没有Symbol.iterator方法,而后者有。

$ node test-program.js generator: [Function: [Symbol.iterator]] asyncGenerator undefined
Nach dem Login kopieren

这两个生成器对象都有一个next方法。如果修改测试代码来调用这个next方法:

// File: test-program.js /* ... */ const main = () => { const generator = createGenerator() const asyncGenerator = createAsyncGenerator() console.log('generator:',generator.next()) console.log('asyncGenerator',asyncGenerator.next()) } main()
Nach dem Login kopieren

则会看到另一个问题:

$ node test-program.js generator: { value: 'a', done: false } asyncGenerator Promise {  }
Nach dem Login kopieren

为了使对象可迭代,next方法需要返回带有valuedone属性的对象。一个async函数将总是返回一个Promise对象。这个特性会带到用异步函数创建的生成器上——这些异步生成器始终会yield一个Promise对象。

这种行为使得async函数的生成器无法实现 javascript 迭代协议。

异步迭代

幸运的是有办法解决这个矛盾。如果看一看async生成器返回的构造函数或类

// File: test-program.js /* ... */ const main = () => { const generator = createGenerator() const asyncGenerator = createAsyncGenerator() console.log('asyncGenerator',asyncGenerator) }
Nach dem Login kopieren

可以看到它是一个对象,其类型或类或构造函数是AsyncGenerator而不是Generator

Heute werfen wir einen Blick auf asynchrone Generatoren und ihre enge Verwandte, die asynchrone Iteration.

Hinweis

: Diese Konzepte solltenfür jedes Javascript gelten, das modernen Spezifikationen folgt, der gesamte Code in diesem Artikel wurde jedoch für die Node.js-Versionen 10, 12 und 14 entwickelt und getestet.
Empfohlenes Video-Tutorial: node js Tutorial

Asynchrone Generatorfunktion

Sehen Sie sich dieses kleine Programm an:
asyncGenerator Object [AsyncGenerator] {}
Nach dem Login kopieren
Dieser Code definiert eine Generatorfunktion, die zum Erstellen eines Generatorobjekts verwendet wird. und verwenden Sie dannfor ... of, um das Generatorobjekt zu durchlaufen. Ziemlich normales Zeug – obwohl man für etwas so Triviales wie dieses eigentlich nie einen Generator verwenden würde. Wenn Sie mit Generatoren undfor ... of-Schleifen nicht vertraut sind, lesen Sie bitte „
Javascript Generator" und " ES6-Schleifen und iterierbare Objekte“ Diese beiden Artikel. Bevor Sie asynchrone Generatoren verwenden, müssen Sie über solide Kenntnisse von Generatoren und for ... of-Schleifen verfügen. Angenommen, wir möchten awaitin der Generatorfunktion verwenden. Node.js unterstützt diese Funktion, solange die Funktion mit dem Schlüsselwort asyncdeklariert werden muss. Wenn Sie mit asynchronen Funktionen nicht vertraut sind, lesen Sie bitte Asynchrones Schreiben in einer modernen JavaScript-Mission< /a>" Artikel. Ändern Sie das Programm unten und verwenden Sieawaitim Generator.
// File: main.js const createAsyncGenerator = async function*(){ yield await new Promise((r) => r('a')) yield 'b' yield 'c' } const main = async () => { const asyncGenerator = createAsyncGenerator() let result = {done:false} while(!result.done) { result = await asyncGenerator.next() if(result.done) { continue; } console.log(result.value) } } main()
Nach dem Login kopieren
Nach dem Login kopieren
Auch im wirklichen Leben würden Sie dies nicht tun – Sie würden wahrscheinlich auf eine Funktion von einer API oder Bibliothek eines Drittanbieterswarten. Damit es für jeden leicht verständlich ist, sind unsere Beispiele so einfach wie möglich gehalten. Wenn Sie versuchen, das obige Programm auszuführen, werden Sie auf ein Problem stoßen:
const createAsyncGenerator = async function*(){ yield await new Promise((r) => r('a')) yield 'b' yield 'c' } const main = async () => { const asyncGenerator = createAsyncGenerator() for await(const item of asyncGenerator) { console.log(item) } } main()
Nach dem Login kopieren
Nach dem Login kopieren
JavaScript teilt uns mit, dass dieser Generator „nicht iterierbar“ ist. Auf den ersten Blick scheint es, dass die asynchrone Funktion eines Generators auch bedeutet, dass der von ihm erzeugte Generator nicht iterierbar ist. Das ist etwas verwirrend, da der Zweck von Generatoren darin besteht, „programmgesteuert“ iterierbare Objekte zu erzeugen. Der nächste Schritt besteht darin, herauszufinden, was passiert ist.

Check Generator

Wenn Sie
Javascript Generator-Artikel, dann sollten Sie wissen, dass, wenn das Objekt die Symbol.iterator-Methode definiert und die Methode zurückgibt, es sich um eine Implementierung in Javascript handelt von Iterator-Protokoll . Wenn ein Objekt über einenext-Methode verfügt, implementiert das Objekt das Iteratorprotokoll und dienext-Methode gibtdone mit einem value-Attribut zurück. Eines der-Attribute oder ein Objekt mit den Attributenvalueunddone. Wenn Sie den folgenden Code verwenden, um die von der asynchronen Generatorfunktion und der regulären Generatorfunktion zurückgegebenen Generatorobjekte zu vergleichen:
$ node main.js a b c
Nach dem Login kopieren
Nach dem Login kopieren
, werden Sie feststellen, dass ersteres keinSymbol.iteratorhat Methode, während letztere haben.
for await(const item of [1,2,3]) { console.log(item) }
Nach dem Login kopieren
Nach dem Login kopieren
Beide Generatorobjekte beide haben einenext-Methode. Wenn Sie den Testcode ändern, um diesenext-Methode aufzurufen:
let count = 0 const getCount = () => { count++ return `${count}. ` } const createAsyncGenerator = async function*() { console.log(getCount() + 'entering createAsyncGenerator') console.log(getCount() + 'about to yield a') yield await new Promise((r)=>r('a')) console.log(getCount() + 're-entering createAsyncGenerator') console.log(getCount() + 'about to yield b') yield 'b' console.log(getCount() + 're-entering createAsyncGenerator') console.log(getCount() + 'about to yield c') yield 'c' console.log(getCount() + 're-entering createAsyncGenerator') console.log(getCount() + 'exiting createAsyncGenerator') } const main = async () => { console.log(getCount() + 'entering main') const asyncGenerator = createAsyncGenerator() console.log(getCount() + 'starting for await loop') for await(const item of asyncGenerator) { console.log(getCount() + 'entering for await loop') console.log(getCount() + item) console.log(getCount() + 'exiting for await loop') } console.log(getCount() + 'done with for await loop') console.log(getCount() + 'leaving main') } console.log(getCount() + 'before calling main') main() console.log(getCount() + 'after calling main')
Nach dem Login kopieren
Nach dem Login kopieren
, sehen Sie ein weiteres Problem: rrreeeUm das Objekt iterierbar zu machen, muss dienext-Methode verwendet werden muss mit einem Objekt mit den Eigenschaftenvalueunddonezurückgegeben werden. Eineasync-Funktion gibt immer einPromise-Objekt zurück. Diese Funktion erstreckt sich auf Generatoren, die mit asynchronen Funktionen erstellt wurden – diese asynchronen Generatorenergebenimmer einPromise-Objekt. Dieses Verhalten macht es Generatoren vonasync-Funktionen unmöglich, das Javascript-Iterationsprotokoll zu implementieren.

Asynchrone Iteration

Glücklicherweise gibt es eine Möglichkeit, diesen Widerspruch aufzulösen. Wenn Sie sich den Konstruktor oder die Klasse ansehen, die vomasync-Generator rrreee zurückgegeben werden, können Sie sehen, dass es sich um ein Objekt handelt, dessen Typ oder Klasse oder KonstruktorAsyncGeneratorund nicht < code ist >Generator: rrreeeObwohl das Objekt möglicherweise nicht iterierbar ist, ist es asynchron iterierbar.

要想使对象能够异步迭代,它必须实现一个Symbol.asyncIterator方法。这个方法必须返回一个对象,该对象实现了异步版本的迭代器协议。也就是说,对象必须具有返回Promisenext方法,并且这个 promise 必须最终解析为带有donevalue属性的对象。

一个AsyncGenerator对象满足所有这些条件。

这就留下了一个问题——我们怎样才能遍历一个不可迭代但可以异步迭代的对象?

for await … of 循环

只用生成器的next方法就可以手动迭代异步可迭代对象。 (注意,这里的main函数现在是async main——这样能够使我们在函数内部使用await

// File: main.js const createAsyncGenerator = async function*(){ yield await new Promise((r) => r('a')) yield 'b' yield 'c' } const main = async () => { const asyncGenerator = createAsyncGenerator() let result = {done:false} while(!result.done) { result = await asyncGenerator.next() if(result.done) { continue; } console.log(result.value) } } main()
Nach dem Login kopieren
Nach dem Login kopieren

但是,这不是最直接的循环机制。我既不喜欢while的循环条件,也不想手动检查result.done。另外,result.done变量必须同时存在于内部和外部块的作用域内。

幸运的是大多数(也许是所有?)支持异步迭代器的 javascript 实现也都支持特殊的for await ... of循环语法。例如:

const createAsyncGenerator = async function*(){ yield await new Promise((r) => r('a')) yield 'b' yield 'c' } const main = async () => { const asyncGenerator = createAsyncGenerator() for await(const item of asyncGenerator) { console.log(item) } } main()
Nach dem Login kopieren
Nach dem Login kopieren

如果运行上述代码,则会看到异步生成器与可迭代对象已被成功循环,并且在循环体中得到了Promise的完全解析值。

$ node main.js a b c
Nach dem Login kopieren
Nach dem Login kopieren

这个for await ... of循环更喜欢实现了异步迭代器协议的对象。但是你可以用它遍历任何一种可迭代对象。

for await(const item of [1,2,3]) { console.log(item) }
Nach dem Login kopieren
Nach dem Login kopieren

当你使用for await时,Node.js 将会首先在对象上寻找Symbol.asyncIterator方法。如果找不到,它将回退到使用Symbol.iterator的方法。

非线性代码执行

await一样,for await循环会将非线性代码执行引入程序中。也就是说,你的代码将会以和编写的代码不同的顺序运行。

当你的程序第一次遇到for await循环时,它将在你的对象上调用next

该对象将yield一个 promise,然后代码的执行将会离开你的async函数,并且你的程序将继续在该函数之外执行

一旦你的 promise 得到解决,代码执行将会使用这个值返回到循环体

当循环结束并进行下一个行程时,Node.js 将在对象上调用next。该调用会产生另一个 promise,代码执行将会再次离开你的函数。重复这种模式,直到 Promise 解析为donetrue的对象,然后在for await循环之后继续执行代码。

下面的例子可以说明一点:

let count = 0 const getCount = () => { count++ return `${count}. ` } const createAsyncGenerator = async function*() { console.log(getCount() + 'entering createAsyncGenerator') console.log(getCount() + 'about to yield a') yield await new Promise((r)=>r('a')) console.log(getCount() + 're-entering createAsyncGenerator') console.log(getCount() + 'about to yield b') yield 'b' console.log(getCount() + 're-entering createAsyncGenerator') console.log(getCount() + 'about to yield c') yield 'c' console.log(getCount() + 're-entering createAsyncGenerator') console.log(getCount() + 'exiting createAsyncGenerator') } const main = async () => { console.log(getCount() + 'entering main') const asyncGenerator = createAsyncGenerator() console.log(getCount() + 'starting for await loop') for await(const item of asyncGenerator) { console.log(getCount() + 'entering for await loop') console.log(getCount() + item) console.log(getCount() + 'exiting for await loop') } console.log(getCount() + 'done with for await loop') console.log(getCount() + 'leaving main') } console.log(getCount() + 'before calling main') main() console.log(getCount() + 'after calling main')
Nach dem Login kopieren
Nach dem Login kopieren

这段代码你用了编号的日志记录语句,可让你跟踪其执行情况。作为练习,你需要自己运行程序然后查看执行结果是怎样的。

如果你不知道它的工作方式,就会使程序的执行产生混乱,但异步迭代的确是一项强大的技术。

更多编程相关知识,请访问:编程入门!!

Das obige ist der detaillierte Inhalt vonEin tiefer Einblick in asynchrone Generatoren und asynchrone Iteration in Node.js. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Verwandte Etiketten:
Quelle:segmentfault.com
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage
Über uns Haftungsausschluss Sitemap
Chinesische PHP-Website:Online-PHP-Schulung für das Gemeinwohl,Helfen Sie PHP-Lernenden, sich schnell weiterzuentwickeln!