<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                企業??AI智能體構建引擎,智能編排和調試,一鍵部署,支持知識庫和私有化部署方案 廣告
                # 異步遍歷器 ## 同步遍歷器的問題 《遍歷器》一章說過,Iterator 接口是一種數據遍歷的協議,只要調用遍歷器對象的`next`方法,就會得到一個對象,表示當前遍歷指針所在的那個位置的信息。`next`方法返回的對象的結構是`{value, done}`,其中`value`表示當前的數據的值,`done`是一個布爾值,表示遍歷是否結束。 ```javascript function idMaker() { let index = 0; return { next: function() { return { value: index++, done: false }; } }; } const it = idMaker(); it.next().value // 0 it.next().value // 1 it.next().value // 2 // ... ``` 上面代碼中,變量`it`是一個遍歷器(iterator)。每次調用`it.next()`方法,就返回一個對象,表示當前遍歷位置的信息。 這里隱含著一個規定,`it.next()`方法必須是同步的,只要調用就必須立刻返回值。也就是說,一旦執行`it.next()`方法,就必須同步地得到`value`和`done`這兩個屬性。如果遍歷指針正好指向同步操作,當然沒有問題,但對于異步操作,就不太合適了。 ```javascript function idMaker() { let index = 0; return { next: function() { return new Promise(function (resolve, reject) { setTimeout(() => { resolve({ value: index++, done: false }); }, 1000); }); } }; } ``` 上面代碼中,`next()`方法返回的是一個 Promise 對象,這樣就不行,不符合 Iterator 協議,只要代碼里面包含異步操作都不行。也就是說,Iterator 協議里面`next()`方法只能包含同步操作。 目前的解決方法是,將異步操作包裝成 Thunk 函數或者 Promise 對象,即`next()`方法返回值的`value`屬性是一個 Thunk 函數或者 Promise 對象,等待以后返回真正的值,而`done`屬性則還是同步產生的。 ```javascript function idMaker() { let index = 0; return { next: function() { return { value: new Promise(resolve => setTimeout(() => resolve(index++), 1000)), done: false }; } }; } const it = idMaker(); it.next().value.then(o => console.log(o)) // 0 it.next().value.then(o => console.log(o)) // 1 it.next().value.then(o => console.log(o)) // 2 // ... ``` 上面代碼中,`value`屬性的返回值是一個 Promise 對象,用來放置異步操作。但是這樣寫很麻煩,不太符合直覺,語義也比較繞。 ES2018 [引入](https://github.com/tc39/proposal-async-iteration)了“異步遍歷器”(Async Iterator),為異步操作提供原生的遍歷器接口,即`value`和`done`這兩個屬性都是異步產生。 ## 異步遍歷的接口 異步遍歷器的最大的語法特點,就是調用遍歷器的`next`方法,返回的是一個 Promise 對象。 ```javascript asyncIterator .next() .then( ({ value, done }) => /* ... */ ); ``` 上面代碼中,`asyncIterator`是一個異步遍歷器,調用`next`方法以后,返回一個 Promise 對象。因此,可以使用`then`方法指定,這個 Promise 對象的狀態變為`resolve`以后的回調函數。回調函數的參數,則是一個具有`value`和`done`兩個屬性的對象,這個跟同步遍歷器是一樣的。 我們知道,一個對象的同步遍歷器的接口,部署在`Symbol.iterator`屬性上面。同樣地,對象的異步遍歷器接口,部署在`Symbol.asyncIterator`屬性上面。不管是什么樣的對象,只要它的`Symbol.asyncIterator`屬性有值,就表示應該對它進行異步遍歷。 下面是一個異步遍歷器的例子。 ```javascript const asyncIterable = createAsyncIterable(['a', 'b']); const asyncIterator = asyncIterable[Symbol.asyncIterator](); asyncIterator .next() .then(iterResult1 => { console.log(iterResult1); // { value: 'a', done: false } return asyncIterator.next(); }) .then(iterResult2 => { console.log(iterResult2); // { value: 'b', done: false } return asyncIterator.next(); }) .then(iterResult3 => { console.log(iterResult3); // { value: undefined, done: true } }); ``` 上面代碼中,異步遍歷器其實返回了兩次值。第一次調用的時候,返回一個 Promise 對象;等到 Promise 對象`resolve`了,再返回一個表示當前數據成員信息的對象。這就是說,異步遍歷器與同步遍歷器最終行為是一致的,只是會先返回 Promise 對象,作為中介。 由于異步遍歷器的`next`方法,返回的是一個 Promise 對象。因此,可以把它放在`await`命令后面。 ```javascript async function f() { const asyncIterable = createAsyncIterable(['a', 'b']); const asyncIterator = asyncIterable[Symbol.asyncIterator](); console.log(await asyncIterator.next()); // { value: 'a', done: false } console.log(await asyncIterator.next()); // { value: 'b', done: false } console.log(await asyncIterator.next()); // { value: undefined, done: true } } ``` 上面代碼中,`next`方法用`await`處理以后,就不必使用`then`方法了。整個流程已經很接近同步處理了。 注意,異步遍歷器的`next`方法是可以連續調用的,不必等到上一步產生的 Promise 對象`resolve`以后再調用。這種情況下,`next`方法會累積起來,自動按照每一步的順序運行下去。下面是一個例子,把所有的`next`方法放在`Promise.all`方法里面。 ```javascript const asyncIterable = createAsyncIterable(['a', 'b']); const asyncIterator = asyncIterable[Symbol.asyncIterator](); const [{value: v1}, {value: v2}] = await Promise.all([ asyncIterator.next(), asyncIterator.next() ]); console.log(v1, v2); // a b ``` 另一種用法是一次性調用所有的`next`方法,然后`await`最后一步操作。 ```javascript async function runner() { const writer = openFile('someFile.txt'); writer.next('hello'); writer.next('world'); await writer.return(); } runner(); ``` ## for await...of 前面介紹過,`for...of`循環用于遍歷同步的 Iterator 接口。新引入的`for await...of`循環,則是用于遍歷異步的 Iterator 接口。 ```javascript async function f() { for await (const x of createAsyncIterable(['a', 'b'])) { console.log(x); } } // a // b ``` 上面代碼中,`createAsyncIterable()`返回一個擁有異步遍歷器接口的對象,`for...of`循環自動調用這個對象的異步遍歷器的`next`方法,會得到一個 Promise 對象。`await`用來處理這個 Promise 對象,一旦`resolve`,就把得到的值(`x`)傳入`for...of`的循環體。 `for await...of`循環的一個用途,是部署了 asyncIterable 操作的異步接口,可以直接放入這個循環。 ```javascript let body = ''; async function f() { for await(const data of req) body += data; const parsed = JSON.parse(body); console.log('got', parsed); } ``` 上面代碼中,`req`是一個 asyncIterable 對象,用來異步讀取數據。可以看到,使用`for await...of`循環以后,代碼會非常簡潔。 如果`next`方法返回的 Promise 對象被`reject`,`for await...of`就會報錯,要用`try...catch`捕捉。 ```javascript async function () { try { for await (const x of createRejectingIterable()) { console.log(x); } } catch (e) { console.error(e); } } ``` 注意,`for await...of`循環也可以用于同步遍歷器。 ```javascript (async function () { for await (const x of ['a', 'b']) { console.log(x); } })(); // a // b ``` Node v10 支持異步遍歷器,Stream 就部署了這個接口。下面是讀取文件的傳統寫法與異步遍歷器寫法的差異。 ```javascript // 傳統寫法 function main(inputFilePath) { const readStream = fs.createReadStream( inputFilePath, { encoding: 'utf8', highWaterMark: 1024 } ); readStream.on('data', (chunk) => { console.log('>>> '+chunk); }); readStream.on('end', () => { console.log('### DONE ###'); }); } // 異步遍歷器寫法 async function main(inputFilePath) { const readStream = fs.createReadStream( inputFilePath, { encoding: 'utf8', highWaterMark: 1024 } ); for await (const chunk of readStream) { console.log('>>> '+chunk); } console.log('### DONE ###'); } ``` ## 異步 Generator 函數 就像 Generator 函數返回一個同步遍歷器對象一樣,異步 Generator 函數的作用,是返回一個異步遍歷器對象。 在語法上,異步 Generator 函數就是`async`函數與 Generator 函數的結合。 ```javascript async function* gen() { yield 'hello'; } const genObj = gen(); genObj.next().then(x => console.log(x)); // { value: 'hello', done: false } ``` 上面代碼中,`gen`是一個異步 Generator 函數,執行后返回一個異步 Iterator 對象。對該對象調用`next`方法,返回一個 Promise 對象。 異步遍歷器的設計目的之一,就是 Generator 函數處理同步操作和異步操作時,能夠使用同一套接口。 ```javascript // 同步 Generator 函數 function* map(iterable, func) { const iter = iterable[Symbol.iterator](); while (true) { const {value, done} = iter.next(); if (done) break; yield func(value); } } // 異步 Generator 函數 async function* map(iterable, func) { const iter = iterable[Symbol.asyncIterator](); while (true) { const {value, done} = await iter.next(); if (done) break; yield func(value); } } ``` 上面代碼中,`map`是一個 Generator 函數,第一個參數是可遍歷對象`iterable`,第二個參數是一個回調函數`func`。`map`的作用是將`iterable`每一步返回的值,使用`func`進行處理。上面有兩個版本的`map`,前一個處理同步遍歷器,后一個處理異步遍歷器,可以看到兩個版本的寫法基本上是一致的。 下面是另一個異步 Generator 函數的例子。 ```javascript async function* readLines(path) { let file = await fileOpen(path); try { while (!file.EOF) { yield await file.readLine(); } } finally { await file.close(); } } ``` 上面代碼中,異步操作前面使用`await`關鍵字標明,即`await`后面的操作,應該返回 Promise 對象。凡是使用`yield`關鍵字的地方,就是`next`方法停下來的地方,它后面的表達式的值(即`await file.readLine()`的值),會作為`next()`返回對象的`value`屬性,這一點是與同步 Generator 函數一致的。 異步 Generator 函數內部,能夠同時使用`await`和`yield`命令。可以這樣理解,`await`命令用于將外部操作產生的值輸入函數內部,`yield`命令用于將函數內部的值輸出。 上面代碼定義的異步 Generator 函數的用法如下。 ```javascript (async function () { for await (const line of readLines(filePath)) { console.log(line); } })() ``` 異步 Generator 函數可以與`for await...of`循環結合起來使用。 ```javascript async function* prefixLines(asyncIterable) { for await (const line of asyncIterable) { yield '> ' + line; } } ``` 異步 Generator 函數的返回值是一個異步 Iterator,即每次調用它的`next`方法,會返回一個 Promise 對象,也就是說,跟在`yield`命令后面的,應該是一個 Promise 對象。如果像上面那個例子那樣,`yield`命令后面是一個字符串,會被自動包裝成一個 Promise 對象。 ```javascript function fetchRandom() { const url = 'https://www.random.org/decimal-fractions/' + '?num=1&dec=10&col=1&format=plain&rnd=new'; return fetch(url); } async function* asyncGenerator() { console.log('Start'); const result = await fetchRandom(); // (A) yield 'Result: ' + await result.text(); // (B) console.log('Done'); } const ag = asyncGenerator(); ag.next().then(({value, done}) => { console.log(value); }) ``` 上面代碼中,`ag`是`asyncGenerator`函數返回的異步遍歷器對象。調用`ag.next()`以后,上面代碼的執行順序如下。 1. `ag.next()`立刻返回一個 Promise 對象。 1. `asyncGenerator`函數開始執行,打印出`Start`。 1. `await`命令返回一個 Promise 對象,`asyncGenerator`函數停在這里。 1. A 處變成 fulfilled 狀態,產生的值放入`result`變量,`asyncGenerator`函數繼續往下執行。 1. 函數在 B 處的`yield`暫停執行,一旦`yield`命令取到值,`ag.next()`返回的那個 Promise 對象變成 fulfilled 狀態。 1. `ag.next()`后面的`then`方法指定的回調函數開始執行。該回調函數的參數是一個對象`{value, done}`,其中`value`的值是`yield`命令后面的那個表達式的值,`done`的值是`false`。 A 和 B 兩行的作用類似于下面的代碼。 ```javascript return new Promise((resolve, reject) => { fetchRandom() .then(result => result.text()) .then(result => { resolve({ value: 'Result: ' + result, done: false, }); }); }); ``` 如果異步 Generator 函數拋出錯誤,會導致 Promise 對象的狀態變為`reject`,然后拋出的錯誤被`catch`方法捕獲。 ```javascript async function* asyncGenerator() { throw new Error('Problem!'); } asyncGenerator() .next() .catch(err => console.log(err)); // Error: Problem! ``` 注意,普通的 async 函數返回的是一個 Promise 對象,而異步 Generator 函數返回的是一個異步 Iterator 對象。可以這樣理解,async 函數和異步 Generator 函數,是封裝異步操作的兩種方法,都用來達到同一種目的。區別在于,前者自帶執行器,后者通過`for await...of`執行,或者自己編寫執行器。下面就是一個異步 Generator 函數的執行器。 ```javascript async function takeAsync(asyncIterable, count = Infinity) { const result = []; const iterator = asyncIterable[Symbol.asyncIterator](); while (result.length < count) { const {value, done} = await iterator.next(); if (done) break; result.push(value); } return result; } ``` 上面代碼中,異步 Generator 函數產生的異步遍歷器,會通過`while`循環自動執行,每當`await iterator.next()`完成,就會進入下一輪循環。一旦`done`屬性變為`true`,就會跳出循環,異步遍歷器執行結束。 下面是這個自動執行器的一個使用實例。 ```javascript async function f() { async function* gen() { yield 'a'; yield 'b'; yield 'c'; } return await takeAsync(gen()); } f().then(function (result) { console.log(result); // ['a', 'b', 'c'] }) ``` 異步 Generator 函數出現以后,JavaScript 就有了四種函數形式:普通函數、async 函數、Generator 函數和異步 Generator 函數。請注意區分每種函數的不同之處。基本上,如果是一系列按照順序執行的異步操作(比如讀取文件,然后寫入新內容,再存入硬盤),可以使用 async 函數;如果是一系列產生相同數據結構的異步操作(比如一行一行讀取文件),可以使用異步 Generator 函數。 異步 Generator 函數也可以通過`next`方法的參數,接收外部傳入的數據。 ```javascript const writer = openFile('someFile.txt'); writer.next('hello'); // 立即執行 writer.next('world'); // 立即執行 await writer.return(); // 等待寫入結束 ``` 上面代碼中,`openFile`是一個異步 Generator 函數。`next`方法的參數,向該函數內部的操作傳入數據。每次`next`方法都是同步執行的,最后的`await`命令用于等待整個寫入操作結束。 最后,同步的數據結構,也可以使用異步 Generator 函數。 ```javascript async function* createAsyncIterable(syncIterable) { for (const elem of syncIterable) { yield elem; } } ``` 上面代碼中,由于沒有異步操作,所以也就沒有使用`await`關鍵字。 ## yield\* 語句 `yield*`語句也可以跟一個異步遍歷器。 ```javascript async function* gen1() { yield 'a'; yield 'b'; return 2; } async function* gen2() { // result 最終會等于 2 const result = yield* gen1(); } ``` 上面代碼中,`gen2`函數里面的`result`變量,最后的值是`2`。 與同步 Generator 函數一樣,`for await...of`循環會展開`yield*`。 ```javascript (async function () { for await (const x of gen2()) { console.log(x); } })(); // a // b ```
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看