1. JavaScript 異步編程指南 — 終極解決方案 Async/Await

        共 5774字,需瀏覽 12分鐘

         ·

        2021-07-15 03:57

        ES7 之后引入了 Async/Await 解決異步編程,這種方式在 JavaScript 異步編程中目前也被稱為 “終極解決方案”。

        基本使用

        函數(shù)聲明時(shí)在 function 關(guān)鍵詞之前使用 async 關(guān)鍵字,內(nèi)部使用 await 替換了 Generator 中的 yield,語(yǔ)義上比起 Generator 中的 * 號(hào)也更明確

        在執(zhí)行時(shí)相比 Generator 而言,Async/Await 內(nèi)置執(zhí)行器,不需要 co 這樣的外部模塊,程序語(yǔ)言本身實(shí)現(xiàn)是最好的,使用上也更簡(jiǎn)單。

        聲明 async 函數(shù)

        以下是基于 Generator 一講中的一個(gè)例子做了改造,在第二個(gè) await 后面,使用 Promise 封裝了下,它本身是支持跟一個(gè) Promise 對(duì)象的,這個(gè)時(shí)候它會(huì)等待當(dāng) Promise 狀態(tài)變?yōu)?Fulfilled 才會(huì)執(zhí)行下一步,當(dāng) Promise 未能正常執(zhí)行 resolve/reject 時(shí)那就意味著,下面的也將得不到執(zhí)行。

        await 后面還可跟上基本類(lèi)型:數(shù)值、字符串、布爾值,但這時(shí)也會(huì)立即轉(zhuǎn)成 Fulfilled 狀態(tài)的 Promise。

        async function test({
          const res1 = await 'A';
          const res2 = await Promise.resolve(res1 + 'B');
          const res3 = await res2 + 'C';
          return res3;
        }

        (async () => {
          const res = await test(); // ABC
        })();

        錯(cuò)誤管理

        如果 await 后面的 Promise 返回一個(gè)錯(cuò)誤,需要 try/catch 做錯(cuò)誤捕獲,若有多個(gè) await 操作也可都放在一起。這種情況,假如第一個(gè) await 后面的 Promise 報(bào)錯(cuò),第二個(gè) await 是不會(huì)執(zhí)行的。

        這和普通函數(shù)操作基本上是一樣的,不同的是對(duì)于異步函數(shù)我們需要加上 await 關(guān)鍵字。

        (async () => {
          try {
           await fetch1(url);
           await fetch2(url);
          } catch (err) {
           // TODO
          }
        })();

        也要注意 await 必須寫(xiě)在 async 函數(shù)里,否則會(huì)報(bào)錯(cuò) SyntaxError: await is only valid in async functions and the top level bodies of modules

        // 錯(cuò)誤的操作
        (() => {
          await 'A';
        })();

        這樣寫(xiě)也是不行的,在 “協(xié)程” 一講中也提過(guò)類(lèi)似的示例,只不過(guò)當(dāng)時(shí)是基于 yield 表達(dá)式,async/await 實(shí)際上是 Generator 函數(shù)的一種語(yǔ)法糖,內(nèi)部機(jī)制是一樣的,forEach 里面的匿名函數(shù)是一個(gè)普通的函數(shù),運(yùn)行時(shí)會(huì)被看作是一個(gè)子函數(shù),棧式協(xié)程是從子函數(shù)產(chǎn)生的,而 ES6 中實(shí)現(xiàn)的協(xié)程屬于無(wú)堆棧式協(xié)程,只能從生成器內(nèi)部生成。以下代碼在運(yùn)行時(shí)會(huì)直接失敗。

        (async () => {
          ['B''C'].forEach(item => {
            const res = await item;
            console.log(res);
          })
        })();

        想通過(guò) await 表達(dá)式正常運(yùn)行,就要避免使用回調(diào)函數(shù),可以使用遍歷器 for...of。

        (async () => {
          for (const item of ['B''C']) {
            const res = await item; // B C
          }
        })();

        并發(fā)執(zhí)行

        當(dāng)我們擁有多個(gè)異步請(qǐng)求,且不必順序執(zhí)行時(shí),可以在 await 表達(dá)式后使用 Promise.all(),這是一個(gè)很好的實(shí)踐。

        (async () => {
          await Promise.all([
           fetch(url1),
            fetch(ur2)
          ])
        })();

        通過(guò)這個(gè)示例可以看出,async/await 也還是基于 Promise 的。

        異步迭代

        上面講解的使用 Async/Await 都是基于單次運(yùn)行的異步函數(shù),在 Node.js 中我們還有一類(lèi)需求它來(lái)自于連續(xù)的事件觸發(fā),例如,基于流式 API 讀取數(shù)據(jù),常見(jiàn)的是注冊(cè) on('data', callback) 事件和回調(diào)函數(shù),但是這樣我們不能利用常規(guī)的 Async/Await 表達(dá)式來(lái)處理這類(lèi)場(chǎng)景。

        異步迭代器

        異步迭代器與同步迭代器不同的是,一個(gè)可迭代的異步迭代器對(duì)象具有  [Symbol.asyncIterator]  屬性,并且返回的是一個(gè) Promise.resolve({ value, done }) 結(jié)果。

        實(shí)現(xiàn)異步迭代器比較方便的方式是使用聲明為 async 的生成器函數(shù),可以使我們像常規(guī)函數(shù)中一樣去使用 await,以下展示了 Node.js 可讀流對(duì)象是如何實(shí)現(xiàn)的異步可迭代,只列出了核心代碼,異步迭代器筆者也有一篇詳細(xì)的文章介紹,很精彩,感興趣的可以看看 探索異步迭代器在 Node.js 中的使用

        // for await...of 循環(huán)會(huì)調(diào)用
        Readable.prototype[SymbolAsyncIterator] = function({
          ...
          const iter = createAsyncIterator(stream);
          return iter;
        };

        // 聲明一個(gè)創(chuàng)建異步迭代器對(duì)象的生成器函數(shù)
        async functioncreateAsyncIterator(stream{
          ...
          try {
            while (true) {
              // stream.read() 從內(nèi)部緩沖拉取并返回?cái)?shù)據(jù)。如果沒(méi)有可讀的數(shù)據(jù),則返回 null
              // readable 的 destroy() 方法被調(diào)用后 readable.destroyed 為 true,readable 即為下面的 stream 對(duì)象
              const chunk = stream.destroyed ? null : stream.read();
              if (chunk !== null) {
                yield chunk; // 這里是關(guān)鍵,根據(jù)迭代器協(xié)議定義,迭代器對(duì)象要返回一個(gè) next() 方法,使用 yield 返回了每一次的值
              }
              ...
            }
          } catch (err) {
          }
        }

        for...await...of 遍歷器

        Node.js Stream 模塊的可讀流對(duì)象在 v10.0.0 版本試驗(yàn)性的支持了 [Symbol.asyncIterator] 屬性,可以使用 for await...of 語(yǔ)句遍歷可讀流對(duì)象,在 v11.14.0 版本以上已 LTS 支持,這使得我們從流中讀取連續(xù)的數(shù)據(jù)塊變的很方便。

        const fs = require('fs');
        const readable = fs.createReadStream('./hello.txt', { encoding'utf-8' });

        async function readText(readable{
          let data = '';
          for await (const chunk of readable) {
            data += chunk;
          }
          return data;
        }

        (async () => {
          try {
            const res = await readText(readable);
            console.log(res); // Hello Node.js
          } catch (err) {
            console.log(err.message);
          }
        })();

        使用 **for await...of** 語(yǔ)句遍歷 readable,如果循環(huán)中因?yàn)?break 或 throw 一個(gè)錯(cuò)誤而終止,則這個(gè) Stream 也將被銷(xiāo)毀。

        頂級(jí) Await

        根據(jù) async 函數(shù)語(yǔ)法規(guī)則,await 只能出現(xiàn)在 async 異步函數(shù)內(nèi)。對(duì)于異步資源,之前我們必須在 async 函數(shù)內(nèi)才可使用 await,這對(duì)一些在文件頂部需要實(shí)例化的資源可能會(huì)不好操作。

        在 Node.js v14.x LTS 發(fā)布后,已支持頂級(jí) Await 我們可以方便的在文件頂部對(duì)這些異步資源做一些初始化操作。

        我們可以像下面這樣來(lái)寫(xiě),但這種模式也只有在 ES Modules 中才可用。

        import fetch from 'node-fetch';
        const res = await fetch(url)

        總結(jié)

        JavaScript 編程中大部分操作都是異步編程,Async/Await 可以已同步的方式來(lái)書(shū)寫(xiě)我們的代碼,但是實(shí)際執(zhí)行其還是異步的,這種被方式目前也稱為異步編程的終極解決方案。

        往期回顧



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

        ● 字節(jié)跳動(dòng)最?lèi)?ài)考的前端面試題:JavaScript 基礎(chǔ)

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



        ·END·

        圖雀社區(qū)

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



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


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

        瀏覽 52
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
          
          

            1. 日本黄色视频免费观看 | 无码字幕| 日韩在线二区 | 西西444WWW无码大胆 | 操女人逼网 |