一つはjavascript promiseの例を使用します.

68484 ワード

複雑な非同期コードは簡単になりました.
OKです.今は実際のコードを書きます.私たちが望むなら:
  • は、ローディング指示アイコン
  • を表示する.
  • は小説のJSONをロードして、小説名と各章の内容のURLを含みます.
  • ページに小説名を記入する
  • すべての章の本文を読み込みます.
  • ページに章本文を追加する
  • ローディング停止指示
  • この過程で何か間違いがあったら、ユーザーに知らせて、ロード指示を止めます.そうしないと、目がくらくらしたり、インターフェースを壊したりします.
    もちろん、JavaScriptを使ってこのように煩雑に文章を表示することはできません.直接HTMLを出力するのはずっと速いですが、このプロセスはとても典型的なAPI要求モードです.
    まず、ネットワークからデータをロードするステップができます.
    PromiseをXMLtpRequestに使用します.
    後方互換性を維持できれば、既存のAPIはPromiseをサポートするために更新され、XMLHttpRequest 対象を重点的に考える一つです.でも、とりあえずGETリクエストを書きます.
    function get(url) {
      //        Promise
      return new Promise(function(resolve, reject) {
        //    XHR   
        var req = new XMLHttpRequest();
        req.open('GET', url);
    
        req.onload = function() {
          //     404            
          //         
          if (req.status == 200) {
            //         ,    Promise
            resolve(req.response);
          }
          else {
            //                Promise
            // (         Error   )
            reject(Error(req.statusText));
          }
        };
    
        //          
        req.onerror = function() {
          reject(Error("Network Error"));
        };
    
        //     
        req.send();
      });
    }
    今呼び出せます.
    get('story.json').then(function(response) {
      console.log("Success!", response);
    }, function(error) {
      console.error("Failed!", error);
    });
    ここをクリックしてコードを確認します.今は手で打つ必要がありません.  XMLHttpRequest 直接HTTPの要求を始めることができて、このように感じるのはずっと良くなって、一回のこの狂うラクダの峰の命名のを少し見ることができます.  XMLHttpRequest 私はもっと楽しいです.
    チェーンコール
    「then」の物語はまだ終わっていません.これらの「then」を直列に修正したり追加したりして、もっと非同期的に操作してもいいです.
    値の処理
    結果を修正して新しい値に戻ります.
    var promise = new Promise(function(resolve, reject) {
      resolve(1);
    });
    
    promise.then(function(val) {
      console.log(val); // 1
      return val + 2;
    }).then(function(val) {
      console.log(val); // 3
    });
    前のコードに戻る:
    get('story.json').then(function(response) {
      console.log("Success!", response);
    });
    受信した応答は純粋なテキストのJSONで、get関数の設定を変更できます.  responseTypeはJSONのためにサーバ応答フォーマットを指定します.Promiseの世界でこの問題を解決することもできます.
    get('story.json').then(function(response) {
      return JSON.parse(response);
    }).then(function(response) {
      console.log("Yey JSON!", response);
    });
    からには  JSON.parse 一つのパラメータだけを受信して、変換後の結果を返します.
    get('story.json').then(JSON.parse).then(function(response) {
      console.log("Yey JSON!", response);
    });
    コード実行ページをクリックして、コントロールを開いて出力結果を確認します. 実は、getJSONをお願いします. 関数の書き方はとても簡単です.
    function getJSON(url) {
      return get(url).then(JSON.parse);
    }
    getJSON JSONを取得して解析するPromiseを返します.
    キューの非同期操作
    「then」を直列にして順番に非同期操作を行うこともできます.
    「then」のコールバック関数から戻ってきたら、ここはちょっと魔法です.値を返したら、次の「then」へのフィードバックがあります.もしあなたが「クラスPromise」の対象に戻ったら、次の「then」はこのPromiseが明確に終了するのを待っています.たとえば:
    getJSON('story.json').then(function(story) {
      return getJSON(story.chapterUrls[0]);
    }).then(function(chapter1) {
      console.log("Got chapter 1!", chapter1);
    });
    ここで私たちは「story.json」に対する非同期の要求を開始し、より多くのURLを返してください.そして、その中の最初の一つをお願いします.Promiseは初めて事件の折り返しに優越性を示しました.章の内容を把握する独立した関数を書くこともできます.
    var storyPromise;
    
    function getChapter(i) {
      storyPromise = storyPromise || getJSON('story.json');
      
      return storyPromise.then(function(story) {
        return getJSON(story.chapterUrls[i]);
      })
    }
    
    //        :
    getChapter(0).then(function(chapter) {
      console.log(chapter);
      return getChapter(1);
    }).then(function(chapter) {
      console.log(chapter);
    });
    私たちは最初にstory.jsonをロードしませんでした.初めてです.  getChapter、その後は毎回getChapter. ロード済みのstory Promiseを再利用することができますので、story.jsonは一回だけお願いします.Promiseは素晴らしいです
    エラー処理
    前に見たように、「then」は二つのパラメータを受け入れ、一つの処理が成功し、一つの処理が失敗しました.
    get('story.json').then(function(response) {
      console.log("Success!", response);
    }, function(error) {
      console.log("Failed!", error);
    });
    「catch」も使えます.
    get('story.json').then(function(response) {
      console.log("Success!", response);
    }).catch(function(error) {
      console.log("Failed!", error);
    });
    ここのcatchは特別なところがありません.  then(undefined, func) の文法糖衣はもっと直観的です.上の二つのコードの行動は同じだけでなく、後者は以下の通りです.
    get('story.json').then(function(response) {
      console.log("Success!", response);
    }).then(undefined, function(error) {
      console.log("Failed!", error);
    });
    大きな違いはないが、意味は非常に大きい.Promiseが否定されると、その後最初に否定反転を配置したthen(またはcatch)にジャンプします.に対する  then(func1, func2) 必ず呼び出します.  func1または  func2 一つは、二つとも呼び出しません.に対する  then(func1).catch(func2) このように  func1 否定を返すと  func2 また、彼らはチェーン呼び出しの独立した2つのステップで呼び出されます.下のコードを見てください.
    asyncThing1().then(function() {
      return asyncThing2();
    }).then(function() {
      return asyncThing3();
    }).catch(function(err) {
      return asyncRecovery1();
    }).then(function() {
      return asyncThing4();
    }, function(err) {
      return asyncRecovery2();
    }).catch(function(err) {
      console.log("Don't worry about it");
    }).then(function() {
      console.log("All done!");
    });
    この流れはJavaScriptのtry/catchのように非常によく似ています.「try」コードブロックで発生したエラーはすぐに「catch」コードブロックにジャンプします.これは上のコードのフローチャートです.
    緑の線は肯定的なPromiseの流れで、赤い糸は否定的なPromiseの流れです.
    JavaScript異常とPromise
    Promiseの否定応答はPromise.reject()によってトリガされてもよいし、コンストラクタのリピートにおいて投げられたエラーによってトリガされてもよい.
    var jsonPromise = new Promise(function(resolve, reject) {
      //            JSON.parse      
      //            :
      resolve(JSON.parse("This ain't JSON"));
    });
    
    jsonPromise.then(function(data) {
      //       :
      console.log("It worked!", data);
    }).catch(function(err) {
      //      :
      console.log("It failed!", err);
    });
    これは、すべてのPromise関連の操作をその構造関数のフィードバックにおいて行うことができ、このように何が起こっても捕まり、Promise否定をトリガすることができるという意味です.
    「then」コールバックで投げたエラーも同じです.
    get('/').then(JSON.parse).then(function() {
      // This never happens, '/' is an HTML page, not JSON
      // so JSON.parse throws
      console.log("It worked!", data);
    }).catch(function(err) {
      // Instead, this happens:
      console.log("It failed!", err);
    });
    実践上のエラー処理
    私たちの物語と章に戻ります.  catch を選択します.
    getJSON('story.json').then(function(story) {
      return getJSON(story.chapterUrls[0]);
    }).then(function(chapter1) {
      addHtmlToPage(chapter1.html);
    }).catch(function() {
      addTextToPage("Failed to show chapter");
    }).then(function() {
      document.querySelector('.spinner').style.display = 'none';
    });
    お願いがあれば  story.chapterUrls[0] 失敗(http 500またはユーザーがドロップするなど)しました.それはスキップした後、成功に対するすべてのコールバックを含みます.  getJSON 応答をJSONのコールバックとして解析し、ここで最初の内容をページに追加したコールバックです.JavaScriptの実行はcatchフィードバックに入ります.結果として、前の章の要求ミスで、ページには「Failed to show chapter」と表示されます.
    JavaScriptのtry/catchと同じように、エラーを捕まえたら、次のコードは継続して実行します.計画通りにロードインジケータを停止します.上のコードは次の段の非ブロック非同期版です.
    try {
      var story = getJSONSync('story.json');
      var chapter1 = getJSONSync(story.chapterUrls[0]);
      addHtmlToPage(chapter1.html);
    }
    catch (e) {
      addTextToPage("Failed to show chapter");
    }
    
    document.querySelector('.spinner').style.display = 'none';
    異常をキャプチャーして記録出力をするだけで、ユーザーインターフェースでエラーをフィードバックするつもりはないなら、Errを捨てればいいです.このステップはErrに置くことができます.  getJSON 中:
    function getJSON(url) {
      return get(url).then(JSON.parse).catch(function(err) {
        console.log("getJSON failed for", url, err);
        throw err;
      });
    }
    今は第一章をクリアしました.これから全章をクリアします.
    並行とシリアル――魚と熊の手のひらを兼ねています.
    異歩の思考方式は直感に合わないです.もしスタートが難しいと感じたら、まず同期の方法を書いてみます.これのようです.
    try {
      var story = getJSONSync('story.json');
      addHtmlToPage(story.heading);
    
      story.chapterUrls.forEach(function(chapterUrl) {
        var chapter = getJSONSync(chapterUrl);
        addHtmlToPage(chapter.html);
      });
    
      addTextToPage("All done");
    }
    catch (err) {
      addTextToPage("Argh, broken: " + err.message);
    }
    
    document.querySelector('.spinner').style.display = 'none';
    これは完全に正常に実行されます.しかし、それは同期されています.コンテンツを読み込む時、ブラウザ全体に引っかかります.それを非同期的に働かせるためには、thenを使ってそれらを次々に連結します.
    getJSON('story.json').then(function(story) {
      addHtmlToPage(story.heading);
    
      // TODO:       story.chapterUrls      url
    }).then(function() {
      //      !
      addTextToPage("All done");
    }).catch(function(err) {
      //               
      addTextToPage("Argh, broken: " + err.message);
    }).then(function() {
      //        spinner    
      document.querySelector('.spinner').style.display = 'none';
    });
    では、私たちはどうやって章のURLを巡回して、順次要求しますか?これはいけません.
    story.chapterUrls.forEach(function(chapterUrl) {
      // Fetch chapter
      getJSON(chapterUrl).then(function(chapter) {
        // and add it to the page
        addHtmlToPage(chapter.html);
      });
    });
    「forEach」は非同期操作に対するサポートがないので、私たちのストーリーパートはそれらのロードが完了した順に表示されます.基本的には「低俗小説」はこのように書かれています.低俗小説を書かないので、修正します.
    シーケンスを作成
    章URL配列をPromiseのシーケンスに変換しますか?  then:
    //          Promise   
    var sequence = Promise.resolve();
    
    //         url
    story.chapterUrls.forEach(function(chapterUrl) {
      //   sequence          
      sequence = sequence.then(function() {
        return getJSON(chapterUrl);
      }).then(function(chapter) {
        addHtmlToPage(chapter.html);
      });
    });
    これは私たちが初めて使うものです.  Promise.resolveは、あなたが送ったどの値によってもPromiseに戻ります.もしあなたがこのクラスのPromiseオブジェクトに伝えたら  then 方法)は、同一の肯定/否定反転を伴うPromiseを生成します.基本的にはクローンです.Promise.resolve('Hello')のような他の値が伝えられたら、この値で完成されたPromiseを作成します.何の値も伝えないなら、undefinedを完成させます.
    もう一つの対応があります.  Promise.reject(val)は、あなたが入ってきたパラメータ(またはundefined)を否定結果としてPromiseを作成します.
    私たちは使えます  array.reduce 上のコードを簡単にしてください.
    //         url
    story.chapterUrls.reduce(function(sequence, chapterUrl) {
      //   sequence          
      return sequence.then(function() {
        return getJSON(chapterUrl);
      }).then(function(chapter) {
        addHtmlToPage(chapter.html);
      });
    }, Promise.resolve());
    前の例と同じ機能ですが、明示的な声明は必要ありません.  sequence 変数reduce 順番に各配列要素に適用されます.第1ラウンドの「sequence」は  Promise.resolve()、その後の呼び出しで「sequence」は前回の関数で実行された結果です.array.reduce 一組の値を一つの値にまとめるには非常に適しています.今のPromiseの使い方です.
    上のコードをまとめます.
    getJSON('story.json').then(function(story) {
      addHtmlToPage(story.heading);
    
      return story.chapterUrls.reduce(function(sequence, chapterUrl) {
        //         Promise     ……
        return sequence.then(function() {
          // ……     
          return getJSON(chapterUrl);
        }).then(function(chapter) {
          //       
          addHtmlToPage(chapter.html);
        });
      }, Promise.resolve());
    }).then(function() {
      //        !
      addTextToPage("All done");
    }).catch(function(err) {
      //            
      addTextToPage("Argh, broken: " + err.message);
    }).then(function() {
      //    spinner      
      document.querySelector('.spinner').style.display = 'none';
    });
    コードの実行例を見ると、前の同期コードは完全に非同期バージョンに改造されています.私たちはもっと進むことができます.現在のページに読み込む効果はこうです.
    ブラウザは複数のファイルを同時に読み込むのが得意です.このように、章を次々にダウンロードする方法は非常に効率的です.私達は同時にすべての章をダウンロードして、全部完成したら一回で解決したいです.ちょうどこのようなAPIがあります.
    Promise.all(arrayOfPromises).then(function(arrayOfResults) {
      //...
    });
    Promise.all 一つのPromise配列をパラメータとして受け入れて、すべてのPromiseが完成したら完成するPromiseを作成します.その完成結果は一つの配列で、先に伝えたすべてのPromiseの完成結果を含んでいます.順序はそれらを導入する配列順序と一致します.
    getJSON('story.json').then(function(story) {
      addHtmlToPage(story.heading);
    
      //      Promise            
      return Promise.all(
        //     URL          Promise   
        story.chapterUrls.map(getJSON)
      );
    }).then(function(chapters) {
      //             JSON,    ……
      chapters.forEach(function(chapter) {
        // ……       
        addHtmlToPage(chapter.html);
      });
      addTextToPage("All done");
    }).catch(function(err) {
      //           
      addTextToPage("Argh, broken: " + err.message);
    }).then(function() {
      document.querySelector('.spinner').style.display = 'none';
    });
    接続状況に応じて、コードの改良は、順序付けよりも数秒速くなります.コードの行数も少ないです.章の読み込みが完了する順序は不明ですが、ページに表示される順序は正確です.
    しかし、それは空間を向上させることです.第一章の内容をロードし終わったら、すぐにページに記入してください.そうすると、他のロードタスクがまだ完成していないうちに、ユーザは読み始めることができます.第三章に着いたら、私たちは黙っています.第二章も着いたら、第二章と第三章の内容をページに記入します.これを類推します.
    このような効果を達成するために、私達は同時にすべての章の内容を要求し、次の順序でページに記入します.
    getJSON('story.json').then(function(story) {
      addHtmlToPage(story.heading);
    
      //     URL          Promise   
      //            
      return story.chapterUrls.map(getJSON)
        .reduce(function(sequence, chapterPromise) {
          //    reduce     Promise   
          //             
          return sequence.then(function() {
            //      sequence               
            return chapterPromise;
          }).then(function(chapter) {
            addHtmlToPage(chapter.html);
          });
        }, Promise.resolve());
    }).then(function() {
      addTextToPage("All done");
    }).catch(function(err) {
      //           
      addTextToPage("Argh, broken: " + err.message);
    }).then(function() {
      document.querySelector('.spinner').style.display = 'none';
    });
    ははは(例を調べて)、魚と熊の掌は兼ねます!すべてのコンテンツをロードする時間は変更されていませんが、ユーザーはより早く第1章を見ることができます.
    この小例では、各章の読み込みがほぼ同時に行われ、章ごとに表示されるポリシーは、章の内容が多い場合にはより優勢になります.
    上のコードを使えば Node.jsスタイルのコールバックやイベントメカニズムが実現すれば、コードの量は大体倍になります.もっと重要なのは可読性もこの例に及ばないです.しかし、Promiseの凄さはこれだけではなく、他のES 6の新機能と組み合わせてさらに効率的に…