[JS]非同期-Promiseとfetch API,Event Loop



コースソース:完全JavaScript Course 2022 Jonas(Udemy)

PromisesとFetch API

XMLHttpRequest로 사이트 받는 형태 
  const request = new XMLHttpRequest();
  request.open('GET', `https://restcountries.com/v2/name/${country}`);
  request.send();

Fetch API

const request = fetch('https://restcountries.com/v2/name/portugal');
console.log(request); // return promise
fetch関数は、すぐに承諾を返す特徴があります.

Promise


Promise:Promiseオブジェクトは、同期操作による将来の成功または失敗とその結果の値を表します.プロセスを作成するときに、不明な値の可能性のあるエージェントを指定し、非同期操作が終了した後に結果値と処理に失敗した理由にプロセッサを接続できます.「≪基本情報|Basic Information|emdw≫」を使用すると、非同期メソッドは同期メソッドのように値を返すことができます.ただし、最終結果ではなく、将来のある時点で結果を提供する「承諾」(Promis)を返します.
簡単に言えば、非同期伝達値を持つコンテナです.あるいは、未来値の容器ともいえる.
将来の値の例:AJAXからの応答
初めて応答を聞いたときは価値がありませんが、これから何が起こるか知ることができます.
Promiseのメリット
  • イベントまたはコールバック関数を必要とせずに、非同期ビジネス処理を実現できます.
    We no longer need to rely on events and callbacks passed into asynchronous functions to handle asynchronous results
  • ネストコールバックの代わりに、承諾チェーンを使用することができる.(callback hellを避ける)
  • Promiseの比喩
    Promiseは宝くじのようです.もし私が正しい結果値を予想したら、私はお金を手に入れます.そうしないと、私は手に入れられません.宝くじの抽選は非同期です.
    lottery ticket that I will receive money if I guess correnct outcome

    The Promise lifecycle

  • Pending(Before the future value is available)
    --Async Task-->
  • Settled (Asynchronous task has finished) ---> fullfilled(success! The value is now available) or rejected (an error happened)
    2つの異なる状況における承諾状態を処理しなければならない.
  • 私たちが約束をしたとき、約束を利用することができます.ex) promise returned from Fetch API

    Consuming Promises


    次のコードから応答のbodyにアクセスするには、jsonを使用する必要があります.jsonはもう一つの承諾が返される特徴を持っている.
    const renderCountry = function (data, className = '') {
      const html = ` <article class="country ${className}">
        <img class="country__img" src="${data.flag}" />
        <div class="country__data">
          <h3 class="country__name">${data.name}</h3>
          <h4 class="country__region">${data.region}</h4>
          <p class="country__row"><span>👫</span>${(
            +data.population / 10000000
          ).toFixed(1)} people</p>
          <p class="country__row"><span>🗣️</span>${data.languages[0].name}</p>
          <p class="country__row"><span>💰</span>${data.currencies[0].name}</p>
        </div>
      </article>`;
      countriesContainer.insertAdjacentHTML('beforeend', html);
      countriesContainer.style.opacity = 1;
    };
    
    const renderError = function (msg) {
      countriesContainer.insertAdjacentText('beforeend', msg);
      countriesContainer.style.opacity = 1;
    };
    const getCountryData = function (country) { 
      fetch(`https://restcountries.com/v2/name/${country}`)
        .then(response => response.json())
        .then(data => renderCountry(data[0]));
    };
    getCountryData('portugal');

    Chaining Promises


    & Handling Rejected Promise

    const getCountryData = function (country) {
      //country 1
      fetch(`https://restcountries.com/v2/name/${country}`)
        .then(response => {
          //Throwing errors manually
          if (!response.ok) throw new Error(`Country not found ${response.status}`);
    
          return response.json();
        })
        .then(data => {
          renderCountry(data[0]);
          const neighbor = data[0].borders[0];
          if (!neighbor) return;
    
          //country 2
          return fetch(`https://restcountries.com/v2/alpha/${neighbor}`);
        })
        .then(response => {
          if (!response.ok) throw new Error(`Country not found ${response.status}`);
          return response.json();
        })
        .then(data => renderCountry(data, 'neighbour'))
        .catch(err => {
          console.error(`${err}💥💥💥`); //Failed to fetch💥💥💥
          renderError(`Something went wrong 💥💥 ${err.message}. Try again!`); //user들도 화면에서 볼 수 있도록
        })
        .finally(() => {
          countriesContainer.style.opacity = 1;
        });
    };
    getJSONという名前の関数簡略化コードを作成する
    const getJSON = function (url, errorMsg = 'Something went wrong') {
      return fetch(url).then(response => {
        if (!response.ok) throw new Error(`${errorMsg} (${response.status})`);
    
        return response.json();
      });
    };
    
    const getCountryData = function (country) {
      //country 1
      getJSON(`https://restcountries.com/v2/name/${country}`, 'Country not found')
        .then(data => {
          renderCountry(data[0]);
          const neighbor = data[0].borders[0];
          if (!neighbor) throw new Error('No neighbor found!');
    
          //country 2
          return getJSON(
            `https://restcountries.com/v2/alpha/${neighbor}`,
            'Country not found'
          );
        })
        .then(data => renderCountry(data, 'neighbour'))
        .catch(err => {
          console.error(`${err}💥💥💥`); //Failed to fetch💥💥💥
          renderError(`Something went wrong 💥💥 ${err.message}. Try again!`); //user들도 화면에서 볼 수 있도록
        })
        .finally(() => {
          countriesContainer.style.opacity = 1;
        });
    };
    
    btn.addEventListener('click', function () {
      getCountryData('portugal');
    });
    
    getCountryData('dfasdfadsf'); 
    // 찾을 수 없는 값을 넣을 경우 promise는 reject로 인식하지 않는다. promise는 오직 internet connection이 되지 않았을 때만 reject로 인식!  error를 undefined (reading 'flag')라고 표현. That's not what we want.
    
    手動でerror処理を行うと、throw new error("")!

    Asynchronous Behind the Scenes : The Event Loop



    Runtime in the Browser : 'Container' which includes all the pieces necessary to execute JavaScript code
    JS engine : "Heart of the runtime"
    Heap : Where object are stored in memory
    Call stack : Where code is actually executed -> only ONE thread of execution. No multitasking!
    WEB API:API privided to the engine(JS内ではありません!)
    Callback Queue : Ready to be executed callback functions(coming from events)
    Concurrency model : How JavaScript handles multiple tasks happening at the same time

    How Asynchronous JavaScript Works Behind the scenes


    el = documemt.querySelector('img');
    el.src = 'dog.jpg'; 
    el.addEventListner('load',()=> {
    el.classList.add('fadeIn');
    });
    fetch('http://someurl.com/api')
     .then(res => console.log(res))
    'dog.jpg'は呼び出しスタック(main thread of execution)内で完了するのではなく、Web API環境自体で完了する.=>ひどうきコード
    WEB API:非同期トランザクションを扱う場所
    画像ロード中、load event内でコールバック関数が待機します.loadが終了すると、コールバックキューにコールバック関数が追加されます.
    データ取得中は、次のコールバック関数webappisで待機します.
    fetchが完了すると、マイクロタスクキューに移動されます.(callback queueより優先されます.イベントループはcallback queueのコールバック関数より先になります.)
    コールバックキューのコールバック関数は、イベントループがcallstackに移動するのを待つ.callスタックの関数が空の場合、すぐに発生します.
    要旨:WEB APIとイベントループがブロックされない非同期コードを実行できるようにする.

    Building a Simple Promise


    new Promise(executer function)
    const lotteryPromise = new Promise(function (resolve, reject) {
      console.log('Lottery draw is happening 💎');
      setTimeout(function () {
        if (Math.random() >= 0.5) {
          resolve('You WIN 💰');
        } else {
          reject(new Error('You lost your money 💩'));
        }
      }, 2000);
    });
    
    lotteryPromise.then(res => console.log(res)).catch(err => console.error(err));
    
    
    別の例-settimeout premization
    const wait = function (seconds) {
      return new Promise(function (resolve) {
        setTimeout(resolve, seconds * 1000);
      });
    };
    wait(2)
      .then(() => {
        console.log('1 second passed');
        return wait(1);
      })
      .then(() => {
        console.log('2 second passed');
        return wait(1);
      })
      .then(() => {
        console.log('3 second passed');
        return wait(1);
      })
      .then(() => {
        console.log('4 second passed');
        return wait(1);
      });
    
    
    콜백지옥과 비교
    setTimeout(() => {
      console.log('1 second passed');
      setTimeout(() => {
        console.log('2 seconds passed');
        setTimeout(() => {
          console.log('3 seconds passed');
        }, 1000);
        setTimeout(() => {
          console.log('4 seconds passed');
        }, 1000);
      }, 1000);
    }, 1000);
    Promise resolveと拒否関数の作成と実行
    Promise.resolve('abc').then(x => console.log(x));
    Promise.reject(new Error('Problem!')).catch(x => console.error(x));

    Promisifying the Geolocation API

    const getPosition = function () {
      return new Promise(function (resolve, reject) {
        // navigator.geolocation.getCurrentPosition(
        //   position => resolve(position),
        //   err => reject(err)
        // );
        navigator.geolocation.getCurrentPosition(resolve, reject);
      });
    };
    
    getPosition()
      .then(pos => console.log(pos))
      .catch(err => console.error(err));
    
    const whereAmI = function () {
      getPosition()
        .then(pos => {
          const { latitude: lat, longitude: lng } = pos.coords;
    
          return fetch(`https://geocode.xyz/${lat},${lng}?geoit=json`);
        })
        .then(response => {
          if (!response.ok)
            throw new Error(`Problem with geocoding ${response.status}`);
          return response.json();
        })
        .then(data => {
          //console.log(data);
          const { region, country } = data;
          console.log(`You are in ${region}`);
          return fetch(`https://restcountries.com/v2/name/${country}`);
        })
        .then(response => {
          if (!response.ok)
            throw new Error(`Country not found (${response.status})`);
    
          return response.json();
        })
        .then(data => renderCountry(data[0]))
        .catch(err => console.error(`${err.message} 💥`));
    };