<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          JavaScript 異步編程指南 — 了解下 Generator 更好的掌握異步編程

          共 7532字,需瀏覽 16分鐘

           ·

          2021-06-25 08:40

          Generator 是 ES6 對協(xié)程的實(shí)現(xiàn),提供了一種異步編程的解決方案,和 Promise 一樣都是線性的模式,相比 Promise 在復(fù)雜的業(yè)務(wù)場景下避免了 .then().then() 這樣的代碼冗余。

          曾經(jīng)一直認(rèn)為 Generator 是一種過渡的解決方案,并沒有過多的去了解它,后來在一些項(xiàng)目中還會看到它的身影,基于它還可以做很多有意思的事情,在不了解的情況下,你無法準(zhǔn)確預(yù)知它的一些行為能夠?qū)е率裁磫栴}。

          例如,Node.js 的可讀流對象在 v10.0.0 版本已試驗(yàn)性的支持了異步迭代器,當(dāng)監(jiān)聽來自可讀流的數(shù)據(jù)時無需在基于事件和回調(diào)的方式 on('data', callback),可以方便的使用 for...await...of 異步迭代,看過源碼會發(fā)現(xiàn)在它的內(nèi)部實(shí)現(xiàn)中是用的異步生成器函數(shù)來生成的異步迭代器。還有目前的 Async/Await 是一種更好的異步解決方案,在下一節(jié)我們會講,本質(zhì)上還是基于 Generator 的語法糖。

          如果想更好的理解 JavaScript 的異步編程,學(xué)習(xí)下 Generator 是沒錯的~

          基本使用

          Generator 函數(shù)聲明

          形式上 Generator 函數(shù)與普通函數(shù)沒太大區(qū)別,兩個特點(diǎn):一是 function 關(guān)鍵字與函數(shù)名之間使用 * 號表達(dá),二是內(nèi)部使用使用 yield 表達(dá)式。

          function *test({
            yield 'A';
            yield 'B';
            yield 'C';
          }

          next()

          如果是普通函數(shù),當(dāng) test() 后函數(shù)會立即執(zhí)行,而生成器函數(shù)調(diào)用后函數(shù)不會立即執(zhí)行,會給我們返回一個迭代器對象。

          調(diào)用 next() 從函數(shù)頭部或上一次暫停的地方執(zhí)行,直到遇到下一個 yield 表達(dá)式暫停或 return 終止,當(dāng)遇到 yield 表達(dá)式暫停后,想要繼續(xù)執(zhí)行下去,需接著調(diào)用 next() 恢復(fù)執(zhí)行。

          next() 返回 yield 表達(dá)式值,當(dāng) done 為 true 時迭代完成。

          const gen = test();
          gen.next() // { value: 'A', done: false }
          gen.next() // { value: 'B', done: false }
          gen.next() // { value: 'C', done: false }
          gen.next() // { value: undefined, done: false }

          return()

          使用 return() 方法返回給定的值,可以強(qiáng)行終止,即使生成器還沒有運(yùn)行完畢。

          const gen = test();
          gen.next() // { value: 'A', done: false }
          gen.next() // { value: 'B', done: false }
          gen.return('termination'); // { value: 'termination', done: true }
          gen.next() // { value: undefined, done: true }

          gen.return() 相當(dāng)于將 yield 語句替換為了 return 表達(dá)式 **yield 'B' 相當(dāng)于 return 'termination'**

          throw()

          生成器函數(shù)返回的迭代器對象還有一個 throw() 方法,在函數(shù)體外拋出錯誤,在函數(shù)體內(nèi)捕獲。需要注意 throw() 方法拋出的錯誤要被內(nèi)部捕獲,必須至少執(zhí)行過一次 next() 方法

          function *test({
            yield 'A';
            try {
              yield 'B';
            } catch (err) {
              console.error('內(nèi)部錯誤', err); // 內(nèi)部錯誤 unknown mistake
            }
            yield 'C';
          }

          const gen = test();
          gen.next()
          gen.next() // { value: 'B', done: false }
          gen.throw('unknown mistake')
          console.log(gen.next()); // { value: undefined, done: true }

          gen.throw() 相當(dāng)于將 yield 語句替換為了 throw 表達(dá)式 **yield 'B' 相當(dāng)于 throw 'unknown mistake'**

          再看 yield 表達(dá)式與 next 方法

          yield 表達(dá)式本身自己沒有值,返回 undefined,可以通過 next() 方法將上一個 yield 表達(dá)式的值做為參數(shù)傳入。

          下面我們將上面示例改下每一個 yiled 依賴前一個 yield 表達(dá)式的返回值。

          function *test({
            const res1 = yield 'A';
            const res2 = yield res1 + 'B';
            const res3 = yield res2 + 'C';
            return res3;
          }

          如果按照上面 gen.next() 不傳入?yún)?shù),結(jié)果只會拿到 undefined。

          以下第一次調(diào)用 gen2.next() 拿到返回值為 A,第二次調(diào)用 next() 時傳入第一次的返回值,test() 函數(shù)內(nèi)部 res1 就可取到第一次 yield 表達(dá)式的值,后面執(zhí)行一樣。

          因?yàn)?next() 傳入的是第一次 yield 表達(dá)式的返回值,所以第一次在調(diào)用 next() 方法時無需傳入?yún)?shù)。

          const gen2 = test();
          const res1 = gen2.next(); // { value: 'A', done: false }
          const res2 = gen2.next(res1.value) // { value: 'AB', done: false }
          const res3 = gen2.next(res2.value) // { value: 'ABC', done: false }
          const res = gen2.next(res3.value); // { value: 'ABC', done: true }
          console.log(res);

          gen.next 相當(dāng)于將 yield 語句替換為了一個表達(dá)式值,例如 gen.next('A')  可以這樣理解 const res2 = yield res1 + 'B'** 相當(dāng)于 **const res2 = 'A' + 'B'

          Generator 與迭代器

          迭代器是通過 next() 方法實(shí)現(xiàn)可迭代協(xié)議的任何一個對象,該方法返回 value 和 done 兩個屬性,其中 value 屬性是當(dāng)前成員的值,done 屬性表示遍歷是否結(jié)束。

          生成器函數(shù)在最初調(diào)用時會返回一種稱為 Generator 的迭代器,這樣可以通過 for...of 遍歷。

          function *test({
            yield 'A';
            yield 'B';
            yield 'C';
            return 'D';
          }
          const gen = test();

          for (const item of gen) {
            console.log(item); // A B C
          }

          有個點(diǎn)需要注意下,for...of 只遍歷到最后一個 yield 關(guān)鍵字,最后一個 return 'D' 忽略掉了,如果使用 next() 是會處理 return 語句的。

          實(shí)例:Generator + 狀態(tài)機(jī)

          Generator 用于實(shí)現(xiàn)狀態(tài)機(jī)還是比較簡單的,也是 JavaScript 里面高級的用法。例如,我們使用 A、B、C 三種狀態(tài)去描述一個事物,狀態(tài)之間是一種有序循環(huán)的,總是 A-B-C-A-B... 永遠(yuǎn)跑不出第 4 種狀態(tài)。

          const state = function* (){
            while(1){
              yield 'A';
              yield 'B';
              yield 'C';
            }
          }
          const status = state();

          setInterval(() => {
            console.log(status.next().value) // A B C A B C A B...
          }, 1000)


          實(shí)例:Generator + Promise

          在 Promise 小節(jié)中我們基于 Promise 做了一次改造,你可以回頭去看下,下面我們使用 Generator 改造后看下差別是什么?

          下例,去掉 yield 關(guān)鍵字和我們使用正常的普通函數(shù)沒什么區(qū)別,為了使 Generator 迭代器對象能夠自動執(zhí)行,還要借助外部模塊 co 實(shí)現(xiàn)。

          co(function *({
            const files = yield fs.readdir(rootDir);
            for (const filname of files) {
              const file = path.resolve(rootDir, filname);
              const stats = yield fs.lstat(file);
              if (stats.isFile()) {
                const chunk = yield fs.readFile(file);
                console.log(chunk.toString());
              }
            }
          });

          總結(jié)

          生成器是一個強(qiáng)大的通用控制結(jié)構(gòu),不像普通函數(shù)那樣調(diào)用之后就直接運(yùn)行到結(jié)束,在程序運(yùn)行過程中當(dāng)遇到 yield 關(guān)鍵字它可以使其保持暫停狀態(tài),直到將來某個時間點(diǎn)繼續(xù)恢復(fù)執(zhí)行。

          在 ES6 中它的最大價值就是管理我們的異步代碼,但是還不是很完美,我們不得不借助類似與 co 這樣的工具來使我們的生成器函數(shù)自動調(diào)用 next() 方法運(yùn)行。不過,在 ES7 到來之后,這一切都過去了,通過 Async/Await 可以更好的管理我們的異步任務(wù)。

          往期回顧


              

          ● 插件式可擴(kuò)展架構(gòu)設(shè)計心得

          ● 字節(jié)跳動最愛考的前端面試題:JavaScript 基礎(chǔ)

          ● 實(shí)現(xiàn)Web端指紋登錄



          ·END·

          圖雀社區(qū)

          匯聚精彩的免費(fèi)實(shí)戰(zhàn)教程



          關(guān)注公眾號回復(fù) z 拉學(xué)習(xí)交流群


          喜歡本文,點(diǎn)個“在看”告訴我

          瀏覽 51
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  大尺度视频在线 | 一级爱爱视频免费看 | 免费看黄色小视频 | 日逼com | 色综合999 |