JavaScriptの約束をマスターする


約束は、非同期操作の最終的な完了または失敗を表すオブジェクトです.単純にコールバック関数を使用しているが、いくつかの簡単で最も重要なのは、コールバック地獄の罠に入ることなく、約束を使用して非同期コードを書くことができることを意味する😉.

約束は何ですか.
約束はコードを非同期的に実行するための構成です.

  • 保留:-初期状態、どちらも果たしていないか拒否された.

  • 成功した実行then ハンドラ.

  • 拒否されましたcatch ハンドラ.
  • return new Promise((resolve, reject) => {
      setTimeout(() => resolve("done"), 1000);
    })
    
    上記のコードはsetTimeout それは1秒でこの場合、“done”という値で約束を解決します.
    以下のコードを考えてください.
    
     function fetchUser(username) {
      fetch('https://api.github.com/users/' + username)
      .then(response => response.json())
      .then( data => {
        const str = JSON.stringify(data, undefined, 2);
        document.getElementById('content').innerHTML = str;
      })
      .catch( error => console.error(error));
    }
    
    fetchUser('saroj990');
    
    
    当初、約束はネイティブのJavascript(ES 6は、Javascriptにネイティブに構築された約束)にはなく、サードパーティ製のライブラリを介して利用できるようになっていたQ , BlueBird . そこで、当時開発されていたライブラリはすべて、非同期性を達成するための専用の個別の約束ライブラリを使用していたでしょう.

    どのように約束を構築するには?
    新しいインスタンスを作成する必要がありますPromise , はresolve and reject 引数として、値を返すときにresolve and reject はエラーで約束を拒絶するために使用されます.
    
    function doAsync() {
      return new Promise((resolve, reject) => {
        const number =  Math.ceil(Math.random() * 10);
        if (number % 2 === 0) {
          setTimeout(() => resolve("even"), 2000);
        } else {
          setTimeout(() => reject("odd"), 2000);
        }
      });
    }
    
    
    我々は1〜10の間の乱数を計算するようなものです.数が偶数であるならば、我々は約束を解決します.値が奇数ならば、我々は約束を拒絶します.
    ここではどのように我々は約束を実行することができます.
    doAsync()
      .then((value) => {
        // success handler
      })
      .catch(err => {
        //log error
      });
    
    
    我々が約束を解決するとき、値はthen ハンドラと拒否の場合、エラーはcatch ハンドラ.

    なぜ我々は約束が必要ですか?
    既に知っているなら👌👌. しかし、我々は我々の話題から逸脱しないように、ここでそれを短くしておきます.
    約束は、コールバック地獄によって浮上した問題を緩和するために導入されました.

    コールバック地獄
    コールバックは引数として別の関数に渡すことができる関数以外にはありません.また、別のコールバックが別のものの中に入れ子になっている場合、コードは本当に理解するのが難しくなります.
    function getUser(id, profile, callback) {
      User.find(id, function (err, user) {
        if(err) {
          callback(err);
        } else {
          user.profile = profile;
          user.save(function(err, user) {
            if(err) { 
              callback(err)
            } else {
              Subscription.findSubscription(id, function(err, subscription) {
                if(err) {
                  callback(err) ;
                } else {
                  user.subscription = subscription;
                  callback(subscription);
                }
              });
            }
          });
        }
      });
    }
    
    上記のコードは悪く見えます、そして、全く表現力がありません、ネスティングのもう一つのレベルが絵に入るとき、状況は本当により悪くなります.
    約束で同じコードを再ファクタしましょう.
    function getUser(id, profile) {
      const currentUser = {};
      return new Promise((resolve, reject) => {
        User
          .find(id)
          .then((user) => {
            currentUser = user;
            currentUser.profile = profile })
          .then(() => Subscription.find(id))
          .then(subscription => {
            currentUser.subscription = subscription;
            return resolve(currentUser)
          })
          .catch(err => reject(err))
      })
    
    }
    
    今、コードは本当にきちんと見えます👌👌. そうですね.それで、それがあなたのコードをより読みやすくて理解しやすくするので、約束を使うことは更なる利点を持ちます.

    約束を連鎖する
    約束連鎖は、ある約束の出力が別の入力になるパターンです.
    ここでは予約を予約しようとしている例です.
    
    Appointment
    .findSlot(time)
    .then(slot => BookAnAppointment(slot.id))
    .then(appointment => FinishPayment(appointment.id))
    .then(payment => getInvoice(payment.id))
    .then(invoice => console.log(invoice))
    .catch(err => console.log(err));
    

    並列実行
    約束が独立して実行される必要があり、他の約束との関係がない状況があります.
    があるPromise.all コンストラクトJavascript これを達成するために並列に約束を実行する見込み.
    // marks a user in-active
    function markInActive(id) {
      return User
      .findById(id)
      .then(user => {
        user.active = false;
        //returns a promise
        return user.save();
      });
    }
    
    // collect the promises into an array
    const promises = []
    for (let i=0; i < ids.length; i++) {
      promises.push(markInActive(ids[i]));
    }
    
    //execute them altogether
    Promise.all(promises)
    .then(result => console.log(result))
    .catch(error => console.log(error));
    
    
    約束と並列実行の連鎖の違いは何でしょうか.さて、例でそれを評価しましょう.
    
    function promiseOne() {
      return new Promise((resolve, reject) => {
        setTimeout(() => resolve('promiseOne'), 1000);
      })
    }
    
    function promiseTwo() {
      return new Promise((resolve, reject) => {
        setTimeout(() => resolve('promiseTwo'), 1000);
      })
    }
    
    function promiseThree() {
      return new Promise((resolve, reject) => {
        setTimeout(() => resolve('promiseThree'), 1000);
      })
    }
    
    約束が連鎖で実行されるとき、2番目の約束は最初の約束が終わるときだけ実行を開始します.
    promiseOne()
    .then((res1) => { 
      console.log(res1);
      return promiseTwo() 
    })
    .then((res2) => {
      console.log(res2);
      return promiseThree();
    }).then(res3 => {
      console.log(res3);
    })
    .catch(err => console.log(err));
    
    /*
    output
    promiseOne
    promiseTwo
    promiseThree
    each promise takes 1sec to execute
    effective time: 3sec
    */
    
    では、同じコードを試してみましょうPromise.all , 並列実行はすべての約束を同時に並列に実行することができます.
    
    Promise.all([ promiseOne(), promiseTwo(), promiseThree()])
      .then(result => console.log(result))
      .catch(err => console.log(err));
    
    
    /*
    output: 
    [ 'promiseOne', 'promiseTwo', 'promiseThree' ]
    all the promises get executed at the same time
    so effective time: 1sec
    */
    

    コールバックを約束に変換する
    あなたがこの点まで続いたならば、あなたはコールバックを約束に変える方法を知っていなければなりません.まず、コールバックを約束に変換する必要がある理由を知る必要があります.
    特定のライブラリ機能が約束の変種メソッドを持っていない時代があります(私は、ほぼすべての図書館が最近約束書インターフェースメソッドを出荷するのを疑います)しかし、あなたはそれを約束として使用したいです.
    function saveUser(payload) {
      return new Promise((resolve, reject) => {
        User.save(payload, function(err, user) {
          if(err) return reject(err);
          return resolve(user);
        });
      });
    }
    
    The User モデルsave メソッドはコールバックメソッドです.Promise 解決して、拒絶する建設.エラーが発生した場合は、エラーのあるプロミスを拒否します.

    エラー処理( catch/finally )
    約束を作成するのは楽しいですが、約束を実行している間に発生する可能性があるエラーを処理しない場合は無駄になります.これを達成するためにcatch ハンドラ関数の引数としてエラーオブジェクトを受け取ります.
    エラーを明示的にスローし、catchブロックで処理するサンプルコードです.
    new Promise((resolve, reject) => {
      reject("some error happened!");
    }).catch(err => console.log(err));
    
    
    我々はまた、約束から明示的なエラーをスローすることができ、それは上記と全く同じです.
    new Promise((resolve, reject) => {
      throw new Error("some error occurred!!")
    }).catch(err => console.log(err));
    
    catchハンドラは、プログラム内で発生した同期または非同期の両方を処理できます.
    我々が意図的にエラーを上げた上記の例では、我々がちょうど見たもの.では、エラーが非同期である別の例を見てみましょう.
    const prom1 = () => new Promise((resolve, reject) =>  {
      setTimeout(() => {
        //rejects after 2sec
        return reject("rejected prom1 promise");
      }, 2000)
    });
    
    new Promise((resolve, reject) => resolve("done"))
      .then(res => prom1())
      .catch(err => console.log(err))
    
    ここでは、最初のメソッドprom1 (非同期的に)約束を拒絶する😉).
    エーthen and catch ブロックは次のように入れ子に入れ子にできます.
    
    new Promise((resolve, reject) => {
      resolve("done")
    }).then(res => {
      console.log("response is : ", res);
      throw new Error("error after the first promise resolved");  // synchronous error
    }).catch(err => {
      console.log("error caught in catch handler", err);
      return "You can rest now"; 
    //simply pass the value to next level
    }).then(res => console.log(res))
    .catch(err => console.log(err)); 
    // prints "you can rest now"
    
    
    
    通常、人々はちょうど約束の終わりに1つのcatchブロックを使用します、そして、どんなエラーが起こるかはちょうどキャッチハンドラーによって捕えられます.

    最後に
    約束のもう一つの重要な部分はfinally ブロックは、約束が成功したり拒否されても実行されます.
    
    new Promise((resolve, reject) => resolve("done"))
    .then(res => console.log(res))
    .catch(err => console.log("I can catch fish too. :)"))
    .finally(() => console.log("I am inevitable, I will always get a chance to execute"))
    
    
    例を挙げてより良い方法で説明しましょう.そうすれば、私たちは、Aを使うことの理由を本当に得ることができますfinally ブロック.
    isLoading = true;
    fetchUser(id)
    .then(user => subscribeToNewsLetter(user.id))
    .then(response => {
      console.log("subscribed to news letter", response);
      // set loader to false once the user info is retrieved
      isLoading = false;
    })
    .catch(err => { 
      console.log(err);
      // in case of error
      isLoading = false;
    });
    
    
    私たちはaを使いますisLoading 変数の起動時に非同期操作が開始されたときに終了したときに、ローダを表示することができます.
    言うまでもなく、我々はisLoading to false つの異なる場所で.
  • 成功ハンドラの中then
  • エラーハンドラ内.これは、何らかのエラーが発生した場合、ローダを永久に継続したくないからです.あなたは?😂😂
  • この実装は効率的ではなく、繰り返し動作する.我々はそれをより良い処理することができますfinally ブロック.
    isLoading = true;
    fetchUser(id)
    .then(user => subscribeToNewsLetter(user.id))
    .then(response => console.log("subscribed to news letter", response))
    .catch(err => console.log(err))
    .finally(() => isLoading = false);
    
    
    Finally ブロックは約束に何が起こるかに関係なく実行されます.したがって、これは我々がいくつかのクリーンアップを行うことができる場所として使用することができますDB , socket 接続等
    あなたがこれまでに作ったならば、おめでとう!😁👍. あなたはこの記事は、JavaScriptの約束を理解し、このポストを好みによってあなたの愛を示すことを躊躇しないように感じている場合.
    あなたが記事の中で何かを改善することができるように感じるならば、コメントを加えてください.私は本当に感謝します.