なぜJavaScriptで最上階のawaitを使う必要がありますか?

10176 ワード

  • 原文の住所:Why Shuld You Use Top-level Await in JavaScript?
  • 原文の作者:Mahdhi Rezvi
  • 翻訳者:Chor
  • 非常に柔軟で強力な言語として、JavaScriptは現代ウェブに深い影響を与えました.ウェブ開発で主導的な地位を占める理由の一つは、頻繁な更新による持続的な改善である.
    最上階のawait(top-level await)は、近年の提案に関わる新たな特性である.この特性は、ESモジュールを外部にasync関数として表現することができ、ESモジュールがawaitデータに行き、他のデータを導入するモジュールをブロックすることができる.データが確定して準備されている場合のみ、データを導入するモジュールがコードを実行できます.
    この特性に関する提案はまだstage 3段階にありますので、直接生産環境では使用できません.しかし、近い未来に発売される予定なので、あらかじめ調べておくといいです.
    霧のように聞こえても大丈夫です.続けて読んでください.あなたと一緒にこの新しい特性を解決します.
    以前の書き方はどこでしたか?
    トップレベルのawaitを導入する前に、async関数の外でawaitキーワードを使用しようとすると、文法的エラーが発生します.この問題を避けるために、開発者は通常すぐに関数式(IIIFE)を実行します.
    await Promise.resolve(console.log('❤️'));
    //  
    
    (async () => {
     await Promise.resolve(console.log('❤️'));
      //❤️
    })();
    しかし、これは氷山の一角にすぎない.
    ES 6を使ってモジュール化すると、導入してエクスポートするシーンがよくあります.この例を見てください.
    //------ 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関数があります.Promiseは時間終了後、resoliveに返されます.これは非同期的な動作であるため(実際のビジネスシーンでは、ここではfetch呼び出しまたは何らかの非同期タスクであるかもしれない)、async IIIIIIFEでawaitを使用して、その実行結果を待つ.一旦promiseがresoliveされると、library.jsから導入された関数を実行し、計算された結果を二つの変数に割り当てます.これはpromiseがresoliveされる前に、両方の変数が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
    上のコードを実行すると、前の2回の印刷で得られたのは全部undefinedで、後の2回の印刷で得られたのは169と13です.どうしてですか?
    これは、async関数が実行される前にmain.jsmiddleware.jsによって導出された変数にアクセスしたからである.覚えていますか?私たちの前にはもう一つのプロミスがあります.レレスを待っています.
    この問題を解決するためには、変数にアクセスする準備ができたら変数を導入するようにモジュールに通知する方法が必要です.
    ソリューション
    上記の問題に対して、広く使われている二つの解決策があります.
    1.Promise表示をエクスポートし初期化する
    IIIFを導き出すことができます.結果にアクセスできるタイミングを決定します.asyncキーワードは、1つの方法を非同期化し、対応するプロミスを返しますを有する.したがって、以下のコードの中で、async IIIIIIIFEはプロミセに戻ります.
    //------ 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でエクスポート結果にアクセスすると、async IIIIIIIIFREがresoliveされてから変数にアクセスすることができます.
    //------ 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
    })
    この案は有効であるが、新しい問題も導入された.
  • みんなはこのモードを標準として守らなければなりません.そして適切なプロミスを見つけて待たなければなりません.
  • 他のモジュールがmain.js内の変数squareOutputおよびdiagonalOutputに依存している場合、同じようなIIIF promiseを再度作成して導出し、他のモジュールが変数に正しくアクセスできるようにする必要がある.
  • この二つの新しい問題を解決するために、第二の案が生まれました.
    2.導出した変数でresove IIIIF promiseに行く
    この方式では、以前のように変数を単独で導出するのではなく、async IIIIFEの戻り値として変数を返します.そうすれば、main.jsはpromiseがresoliveされるのを簡単に待つだけで、その後直接変数を取得すればいいです.
    //------ 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
    })
    しかし、この案にはその自身の複雑さがある.
    提案によると、このようなモードの悪影響は、動的モードを使用するために関連するデータを大規模に再構成することを要求することである.一方、モジュールの大部分のコンテンツを.then()のコールバック関数において動的に導入することができる.静的分析、試験可能性、工学および他の観点から、ES 2015のモジュール化に比べて明らかである.後戻りする
    トップレベルのAwaitはどうやって上記の問題を解決しますか?
    トップawaitは、モジュールシステムにpromise間の協調関係を処理させ、こちらの作業を非常に簡単にすることができます.
    //------ 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がresoliveされる前に、main.jsの中のいずれかの文が実行されません.前に述べた解決策と比べて、この方法はより簡潔でなければなりません.
    注意
    トップレベルのawaitはESモジュールだけで有効です.また、モジュール間の依存関係を明示してこそ、トップレベルのawaitが期待通りに有効になることができます.提案倉庫のコードはこの問題をよく説明しました.
    // 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
    このコードの印刷の順序は予想されるX1,X2,Yではない.xyは独立したモジュールであり、互いに依存関係がないからである.
    文書クイズを読むことをお勧めします.この最上階のawaitという新しい特性をより全面的に知ることができます.
    仮採用
    V 8
    文書によって言われたように、トップawaitの特性を使ってみてもいいです.
    私が使っているのはV 8の方法です.あなたのコンピュータ上のChromeブラウザの設置場所を見つけて、ブラウザを閉じるすべてのプロセスを確保して、コマンドラインを開いて次のコマンドを実行します.
    chrome.exe --js-flags="--harmony-top-level-await"
    このようにChromeが再オープンすると最上階のawait特性に対するサポートがオープンします.
    もちろん、Node環境テストもできます.このガイドを読んで、より多くの詳細を取得します.
    ESモジュールscriptタグにこの属性を宣言することを保証する:type="module"
    
    
    通常のスクリプトとは異なり、モジュール化後のスクリプトはCORSポリシーの影響を受けますので、サーバーを介してファイルを開く必要があります.
    アプリケーションシーン
    提案で述べた関連例は以下の通りです.
    動的依存経路
    const strings = await import(`/i18n/${navigator.language}`);
    モジュールの運転時の値を使って依存関係を計算することができます.これは生産・開発環境の区分や国際的な仕事などに有効です.
    リソース初期化
    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は多くのバッシング的な問題を提案している.
  • トップawaitは、コードの実行をブロックします.
  • トップawaitは、リソースの取得をブロックします.
  • Common JSモジュールは、明確な相互操作スキームを持っていません.
    ステージ3の提案は、これらの問題を直接解決しました.
  • 兄弟モジュールが実行できるので、ブロックは存在しません.
  • トップawaitは、モジュール図の実行段階で機能し、このとき、すべてのリソースがすでに取得され、リンクされているので、リソースがブロックされるリスクは存在しない.
  • 最上階部awaitはES 6モジュールに限定されており、それ自体は通常のスクリプトまたはCommonJSモジュール
  • をサポートするつもりはない.
    読者の皆さんには、提案したFAQを読んで、この新しい特性に対する理解を深めることを強く勧めます.
    ここを見て、このクールな新しい特性についてある程度知っていると思います.もうこれを使ってみたいですか?コメントエリアで一緒に交流しましょう.