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

          【JS】874- 為何在 JavaScript 中使用頂層 await?

          共 7771字,需瀏覽 16分鐘

           ·

          2021-02-21 09:13

          原文地址:Why Should You Use Top-level Await in JavaScript?[1]
          原文作者:Mahdhi Rezvi[2]
          譯者:Chor

          作為一門非常靈活和強大的語言,JavaScript 對現(xiàn)代 web 產(chǎn)生了深遠的影響。它之所以能夠在 web 開發(fā)中占據(jù)主導地位,其中一個主要原因就是頻繁更新所帶來的持續(xù)改進。

          頂層 await(top-level await)是近年來提案中涉及的新特性。該特性可以讓 ES 模塊對外表現(xiàn)為一個 async 函數(shù),允許 ES 模塊去 await 數(shù)據(jù)并阻塞其它導入這些數(shù)據(jù)的模塊。只有在數(shù)據(jù)確定并準備好的時候,導入數(shù)據(jù)的模塊才可以執(zhí)行相應(yīng)的代碼。

          有關(guān)該特性的提案目前仍處于 stage 3 階段,因此我們不能直接在生產(chǎn)環(huán)境中使用。但鑒于它將在不久的未來推出,提前了解一下還是大有好處的。

          聽起來一頭霧水沒關(guān)系,繼續(xù)往下閱讀,我會和你一起搞定這個新特性的。

          以前的寫法,問題在哪里?

          在引入頂層 await 之前,如果你試圖在一個 async 函數(shù)外面使用 await 關(guān)鍵字,將會引起語法錯誤。為了避免這個問題,開發(fā)者通常會使用立即執(zhí)行函數(shù)表達式(IIFE)

          await?Promise.resolve(console.log('??'));
          //報錯

          (async?()?=>?{
          ?await?Promise.resolve(console.log('??'));
          ??//??
          })();

          然而這只是冰山一角

          在使用 ES6 模塊化的時候,經(jīng)常會遇到需要導入導出的場景??纯聪旅孢@個例子:

          //------?library.js?------
          export?const?sqrt?=?Math.sqrt;
          export?function?square(x)?{
          ????return?x?*?x;
          }
          export?function?diagonal(x,?y)?{
          ????return?sqrt(square(x)?+?square(y));
          }


          //------?middleware.js?------
          import?{?square,?diagonal?}?from?'./library.js';

          console.log('From?Middleware');

          let?squareOutput;
          let?diagonalOutput;

          //?IIFE
          ?(async?()?=>?{
          ??await?delay(1000);
          ??squareOutput?=?square(13);
          ??diagonalOutput?=?diagonal(12,?5);
          ?})();

          function?delay(delayInms)?{
          ??return?new?Promise(resolve?=>?{
          ????setTimeout(()?=>?{
          ??????resolve(console.log('??'));
          ????},?delayInms);
          ??});
          }

          export?{squareOutput,diagonalOutput};

          在這個例子中,我們在library.jsmiddleware.js 之間進行變量的導入導出 (文件名隨意,這里不是重點)

          如果仔細閱讀,你會注意到有一個 delay 函數(shù),它返回的 Promise 會在計時結(jié)束之后被 resolve。因為這是一個異步操作(在真實的業(yè)務(wù)場景中,這里可能會是一個 fetch 調(diào)用或者某個異步任務(wù)),我們在 async IIFE 中使用 await 以等待其執(zhí)行結(jié)果。一旦 promise 被 resolve,我們會執(zhí)行從 library.js 中導入的函數(shù),并將計算得到的結(jié)果賦值給兩個變量。這意味著,在 promise 被 resolve 之前,兩個變量都會是 undefined。

          在代碼最后面,我們將計算得到的兩個變量導出,供另一個模塊使用。

          下面這個模塊負責導入并使用上述兩個變量:

          //------?main.js?------
          import?{?squareOutput,?diagonalOutput?}?from?'./middleware.js';

          console.log(squareOutput);?//?undefined
          console.log(diagonalOutput);?//?undefined
          console.log('From?Main');

          setTimeout(()?=>?console.log(squareOutput),?2000);
          //169

          setTimeout(()?=>?console.log(diagonalOutput),?2000);
          //13

          運行上面代碼,你會發(fā)現(xiàn)前兩次打印得到的都是 undefined,后兩次打印得到的是 169 和 13。為什么會這樣呢?

          這是因為,在 async 函數(shù)執(zhí)行完畢之前,main.js 就已經(jīng)訪問了 middleware.js 導出的變量。記得嗎?我們前面還有一個 promise 等待被 resolve 呢 ……

          為了解決這個問題,我們需要想辦法通知模塊,讓它在準備好訪問變量的時候再將變量導入。

          解決方案

          針對上述問題,有兩個廣泛使用的解決方案:

          1.導出一個 Promise 表示初始化

          你可以導出一個 IIFE 并依靠它確定可以訪問導出結(jié)果的時機。async 關(guān)鍵字可以異步化一個方法,并相應(yīng)返回一個 promise[3]。因此,下面的代碼中,async IIFE 會返回一個 promise。

          //------?middleware.js?------
          import?{?square,?diagonal?}?from?'./library.js';

          console.log('From?Middleware');

          let?squareOutput;
          let?diagonalOutput;

          //解決方案
          export?default?(async?()?=>?{
          ?await?delay(1000);
          ?squareOutput?=?square(13);
          ?diagonalOutput?=?diagonal(12,?5);
          })();

          function?delay(delayInms)?{
          ??return?new?Promise(resolve?=>?{
          ????setTimeout(()?=>?{
          ??????resolve(console.log('??'));
          ????},?delayInms);
          ??});
          }

          export?{squareOutput,diagonalOutput};

          當你在 main.js 中訪問導出結(jié)果的時候,你可以靜待 async IIFE 被 resolve,之后再去訪問變量。

          //------?main.js?------
          import?promise,?{?squareOutput,?diagonalOutput?}?from?'./middleware.js';

          promise.then(()=>{
          ??console.log(squareOutput);?//?169
          ??console.log(diagonalOutput);?//?13
          ??console.log('From?Main');

          ??setTimeout(()?=>?console.log(squareOutput),?2000);//?169

          ??setTimeout(()?=>?console.log(diagonalOutput),?2000);//?13
          })

          盡管這個方案可以生效,但它也引入了新的問題:

          • 大家都必須將這種模式作為標準去遵循,而且必須要找到并等待合適的 promise;
          • 倘若有另一個模塊依賴 main.js 中的變量 squareOutputdiagonalOutput,那么我們就需要再次書寫類似的 IIFE promise 并導出,從而讓另一個模塊得以正確地訪問變量。

          為了解決這兩個新問題,第二個方案應(yīng)運而生。

          2.用導出的變量去 resolve IIFE promise

          在這個方案中,我們不再像之前那樣單獨導出變量,而是將變量作為 async IIFE 的返回值返回。這樣的話,main.js 只需簡單地等待 promise 被 resolve,之后直接獲取變量即可。

          //------?middleware.js?------
          import?{?square,?diagonal?}?from?'./library.js';

          console.log('From?Middleware');

          let?squareOutput;
          let?diagonalOutput;

          export?default?(async?()?=>?{
          ?await?delay(1000);
          ?squareOutput?=?square(13);
          ?diagonalOutput?=?diagonal(12,?5);
          ?return?{squareOutput,diagonalOutput};
          })();

          function?delay(delayInms)?{
          ??return?new?Promise(resolve?=>?{
          ????setTimeout(()?=>?{
          ??????resolve(console.log('??'));
          ????},?delayInms);
          ??});
          }

          //------?main.js?------

          import?promise?from?'./middleware.js';

          promise.then(({squareOutput,diagonalOutput})=>{
          ?console.log(squareOutput);?//?169
          ?console.log(diagonalOutput);?//?13
          ?console.log('From?Main');

          ?setTimeout(()?=>?console.log(squareOutput),?2000);//?169

          ?setTimeout(()?=>?console.log(diagonalOutput),?2000);//?13
          })

          但這個方案有其自身的復(fù)雜性存在。

          根據(jù)提案的說法,“這種模式的不良影響在于,它要求對相關(guān)數(shù)據(jù)進行大規(guī)模重構(gòu)以使用動態(tài)模式;同時,它將模塊的大部分內(nèi)容放在 .then() 的回調(diào)函數(shù)中,以使用動態(tài)導入。從靜態(tài)分析、可測試性、工程學以及其它角度來講,這種做法相比 ES2015 的模塊化來說是一種顯而易見的倒退”。

          頂層 Await 是如何解決上述問題的?

          頂層 await 允許我們讓模塊系統(tǒng)去處理 promise 之間的協(xié)調(diào)關(guān)系,從而讓我們這邊的工作變得異常簡單。

          //------?middleware.js?------
          import?{?square,?diagonal?}?from?'./library.js';

          console.log('From?Middleware');

          let?squareOutput;
          let?diagonalOutput;

          //使用頂層?await
          await?delay(1000);
          squareOutput?=?square(13);
          diagonalOutput?=?diagonal(12,?5);

          function?delay(delayInms)?{
          ??return?new?Promise(resolve?=>?{
          ????setTimeout(()?=>?{
          ??????resolve(console.log('??'));
          ????},?delayInms);
          ??});
          }

          export?{squareOutput,diagonalOutput};

          //------?main.js?------
          import?{?squareOutput,?diagonalOutput?}?from?'./middleware.js';

          console.log(squareOutput);?//?169
          console.log(diagonalOutput);?//?13
          console.log('From?Main');

          setTimeout(()?=>?console.log(squareOutput),?2000);//?169

          setTimeout(()?=>?console.log(diagonalOutput),?2000);?//?13

          middleware.js 中的 await promise 被 resolve 之前, main.js 中任意一條語句都不會執(zhí)行。與之前提及的解決方案相比,這個方法要簡潔得多。

          注意

          必須注意的是,頂層 await 只在 ES 模塊中生效。 此外,你必須要顯式聲明模塊之間的依賴關(guān)系,才能讓頂層 await 像預(yù)期那樣生效。提案倉庫中的這段代碼就很好地說明了這個問題:

          //?x.mjs
          console.log("X1");
          await?new?Promise(r?=>?setTimeout(r,?1000));
          console.log("X2");
          //?y.mjs
          console.log("Y");
          //?z.mjs
          import?"./x.mjs";
          import?"./y.mjs";
          //X1
          //Y
          //X2

          這段代碼打印的順序并不是預(yù)想中的 X1,X2,Y。這是因為 xy 是獨立的模塊,互相之間沒有依賴關(guān)系。

          推薦你閱讀一下 文檔問答[4] ,這樣會對這個頂層 await 這個新特性有更加全面的了解。

          試用

          V8

          你可以按照文檔[5]所說的,嘗試使用頂層 await 特性。

          我使用的是 V8 的方法。找到你電腦上 Chrome 瀏覽器的安裝位置,確保關(guān)閉瀏覽器的所有進程,打開命令行運行如下命令:

          chrome.exe --js-flags="--harmony-top-level-await"

          這樣,Chrome 重新打開后將開啟對于頂層 await 特性的支持。

          當然,你也可以在 Node 環(huán)境測試。閱讀這個指南[6] 獲取更多細節(jié)。

          ES 模塊

          確保在 script 標簽中聲明該屬性:type="module"

          <script?type="module"?src="./index.js"?>
          script>

          需要注意的是,和普通腳本不一樣,聲明模塊化之后的腳本會受到 CORS 策略的影響,因此你需要通過服務(wù)器打開該文件。

          應(yīng)用場景

          以下是提案[7]中講到的相關(guān)用例:

          動態(tài)的依賴路徑

          const?strings?=?await?import(`/i18n/${navigator.language}`);

          允許模塊使用運行時的值去計算得到依賴關(guān)系。這對生產(chǎn)/開發(fā)環(huán)境的區(qū)分以及國際化工作等非常有效。

          資源初始化

          const?connection?=?await?dbConnector();

          這有助于把模塊看作某種資源,同時可以在模塊不存在的時候拋出錯誤。錯誤可以在下面介紹的后備方案中得到處理。

          依賴的后備方案

          下面的例子展示了如何用頂層 await 去加載帶有后備方案的依賴。如果 CDN A 無法導入 jQuery,那么會嘗試從 CDN B 中導入。

          let?jQuery;
          try?{
          ??jQuery?=?await?import('https://cdn-a.example.com/jQuery');
          }?catch?{
          ??jQuery?=?await?import('https://cdn-b.example.com/jQuery');
          }

          抨擊

          針對頂層 await 的特性,Rich Harris 提出了不少抨擊性的問題[8]

          • 頂層 await 會阻塞代碼的執(zhí)行
          • 頂層 await 會阻塞資源的獲取
          • CommonJS 模塊沒有明確的互操作方案

          而 stage 3 的提案已經(jīng)直接解決了這些問題:

          • 由于兄弟模塊能夠執(zhí)行,所以不存在阻塞;
          • 頂層 await 在模塊圖的執(zhí)行階段發(fā)揮作用,此時所有的資源都已經(jīng)獲取并鏈接了,因此不存在資源被阻塞的風險;
          • 頂層await 只限于在 ES6 模塊中使用,本身就不打算支持普通腳本或者 CommonJS 模塊

          我強烈推薦各位讀者閱讀提案的 FAQ[9] 來加深對這個新特性的理解。

          看到這里,想必你對這個酷炫的新特性已經(jīng)有了一定的了解。是不是已經(jīng)迫不及待要使用看看了呢?在評論區(qū)留言一起交流吧。

          參考資料[1]

          Why Should You Use Top-level Await in JavaScript?:?https://blog.bitsrc.io/why-should-you-use-top-level-await-in-javascript-a3ba8139ef23

          [2]

          Mahdhi Rezvi:?https://medium.com/@mahdhirezvi?source=post_page-----a3ba8139ef23

          [3]

          async function :?https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function

          [4]

          文檔問答:?https://github.com/tc39/proposal-top-level-await#faq

          [5]

          文檔:?https://github.com/tc39/proposal-top-level-await#implementations

          [6]

          指南:?https://medium.com/@pprathameshmore/top-level-await-support-in-node-js-v14-3-0-8af4f4a4d478

          [7]

          提案:?https://github.com/tc39/proposal-top-level-await#use-cases

          [8]

          抨擊性的問題:?https://gist.github.com/Rich-Harris/0b6f317657f5167663b493c722647221

          [9]

          FAQ:?https://github.com/tc39/proposal-top-level-await#faq

          1. JavaScript 重溫系列(22篇全)
          2. ECMAScript 重溫系列(10篇全)
          3. JavaScript設(shè)計模式 重溫系列(9篇全)
          4.?正則 / 框架 / 算法等 重溫系列(16篇全)
          5.?Webpack4 入門(上)||?Webpack4 入門(下)
          6.?MobX 入門(上)?||??MobX 入門(下)
          7. 100+篇原創(chuàng)系列匯總

          回復(fù)“加群”與大佬們一起交流學習~

          點擊“閱讀原文”查看 100+ 篇原創(chuàng)文章

          瀏覽 45
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  丁香五月激情综合婷婷 | 免费午夜福利视频 | av大全在线观看 av电影在线一区 | 成人肏屄视频 | 另类一区二区 |