fetch, promise, async, await

jinz.develog·2023년 4월 25일

콜백지옥

const total = 비동기통신함수( 
        input,
        통신함수의결과를가공하는함수1 ( 
            result, 
            비동기통신함수2(
                통신함수의결과를가공하는함수2(
                    result, 
                    result2
                )
            ) 
        ) 
    );

// 이렇게 비동기 함수가 끝나기 전에 중간 중간에 필요한 콜백함수를 실행시키며 사용할 수 밖에 없었습니다.

console.log 계속 찍어도 비동기가 나중에 실행되는 이유 알려주세요

콘솔로그먼저 실행을해줘요
메인스레드에 올라가서 실행해야되는 함수가 오면 메인스레드에있는거 먼저 하고 그다음에 셋타임함수에 있는걸 실행
콘솔로그가 아무리 찍혀도 비동기 함수가 끝나지않은거처럼 실행이안됌

fetch API

XMLHttpRequest 를 대체할 새로운 API 가 등장했으니 이름하여 fetch 입니다. fetch는 여러가지 뜻이 있지만 여기서는 ‘가져오다, 찾아내다’의 의미로 사용되고 있습니다.

XHR과 fetch의 가장 큰 차이점은 XMLHttpRequest 가 생성하는 인스턴스는 통신의 기능을 수행하는 XMLHttpRequest 객체를 반환했지만 fetch는 인스턴스를 만들지 않고, 대신 ‘약속’을 반환한다는 것입니다.

promise

서버한테 통신보낼때 서버에서 뭐가날라올진 모르겠지만 서버는 우리한테 약속을 합니다.
=>그게 fetch를 통해 전달받는 promise라는 객체

[state1]커피 주문전에 뭐 고를지 고민(pending 보류)
[state2]오늘의커피를 주문할때, 오늘의 커피는 ~정도 걸립니다. 완료되면 알려드리겠습니다(promise)
커피 만들어질동안 공부(비동기)
[state3]
1)커피 만들어져서 내옴(resolved) or 2)커피원두 떨어져서 안될거같아요/약속을 했엇지만 거절당함(rejected)

// 커피를 주문하는 프로미스 객체를 생성합니다. 생성자에는 약속을 지키기 위한 resolve와, 약속을 지키지 못했을 때를 대비한 reject 두 가지를 인자로 전달합니다. :executor함수
const orderCoffee = new Promise((resolve, reject) => {

  const requestObj = new XMLHttpRequest();
  requestObj.open('GET', 'orderCoffee.txt');
  requestObj.onreadystatechange = () => {
      if (requestObj.readyState === 4 && requestObj.status === 200) {

          const result = requestObj.responseText;

          // resolve 메소드가 실행되면 then 메소드가 자동으로 호출됩니다.
          resolve(result);

          // resolve 메소드 호출이 없는 상태에서 reject 메소드가 실행되거나 통신에 문제가 발생하면 catch 메소드가 자동으로 호출됩니다.
          // reject(result);
      }
  };
  requestObj.send();

});

// 이 부분에 주목해주세요. then 메소드를 사용하면 비동기 코드를 마치 동기적인 코드처럼 작성할 수 있습니다. 앞에서 작성한 XHR 코드와 비교해보는것도 좋습니다. 

// resolve 메소드가 실행될때 전달된 인자는 then 메소드의 콜백함수의 인자로 전달됩니다.
orderCoffee.then((asyncResult) => {
  console.log(asyncResult);
  console.log('약속이 이루어졌습니다.');
  return asyncResult; 
}).catch((error) => { // then 메소드는 프라미스 객체를 반환하기 때문에 catch 메소드를 이어서 쓰는것이 가능합니다.
  console.log(new Error('커피주문이 정상적으로 이뤄지지 않았습니다.'));
})

executor함수는 resolve, reject 두 인자를 받음

resolve함수 실행시 then 메소드 실행,
reject함수 실행시 catch 메소드 실행

->promise로 인해 콜백지옥 해방!
프로미스는 이행되거나 거절되거나 둘중 한가지 결과만 가짐
둘중 하나의 결과는 있겠지라고 생각하고 작업진행하기 때문에 then, catch로 동기적으로 진행함!

 <script>
    'use strict';

    class UserStorage {

      // 유저가 등록되었는지 확인
      searchUser(userName, password, onSuccess, onError) {
        const xhr = new XMLHttpRequest();
        xhr.open('GET', 'users.json');
        xhr.onreadystatechange = () => {
          if (xhr.readyState === 4 && xhr.status === 200) {
            console.log(xhr.responseText);
            const result = JSON.parse(xhr.responseText).user.find((item) => {
              return item.userName === userName && item.password === password
            });

            if (result) {
              onSuccess(userName);
            } else {
              onError(new Error('user not found'));
            }
          }
        }

        xhr.send();
      }

      // 등록된 유저에 따른 인사말을 출력
      sayHi(user, onSuccess, onError) {
        const xhr = new XMLHttpRequest();
        xhr.open('GET', 'greetings.json');
        xhr.onreadystatechange = () => {
          if (xhr.readyState === 4 && xhr.status === 200) {
            console.log(xhr.responseText);
            const result = JSON.parse(xhr.responseText).greetings.find((item) => {
              return item.userName === user
            });

            if (result) {
              onSuccess(
                {
                  name: result.userName,
                  greetings: result.greetings
                }
              );
            } else {
              onError(new Error('user not found'));
            }
          }
        }

        xhr.send();
      }
    }
    const userStorage = new UserStorage();

    const userName = prompt('이름을 말해보세요')
    const password = prompt('비밀번호를 말해보세요')
    userStorage.searchUser(
      userName,
      password,
      (name) => {
        userStorage.sayHi(
          
        )
      }
    )


  </script>

XMLHttpRequest promise로 바꾸기

onSuccess onError 없애기!

 <script>
      'use strict';

      class UserStorage {

          // 유저가 등록되었는지 확인
          searchUser(userName, password) {

              return new Promise((resolve, reject) => {

                  const xhr = new XMLHttpRequest();
                  xhr.open('GET', 'users.json');
                  xhr.onreadystatechange = () => {
                      if (xhr.readyState === 4 && xhr.status === 200) {

                          const result = JSON.parse(xhr.responseText).user.find((item) => {
                              return item.userName === userName && item.password === password
                          });

                          if (result) {
                              resolve(userName);
                              // onSuccess(userName);
                          } else {
                              reject('user not found');
                              // onError(new Error('user not found'));
                          }
                      }
                  }

                  xhr.send();
              })
          }

          // 등록된 유저에 따른 인사말을 출력
          sayHi(user) {

              return new Promise((resolve, reject) => {
                  const xhr = new XMLHttpRequest();
                  xhr.open('GET', 'greetings.json');
                  xhr.onreadystatechange = () => {
                      if (xhr.readyState === 4 && xhr.status === 200) {
                          console.log(xhr.responseText);
                          const result = JSON.parse(xhr.responseText).greetings.find((item) => {
                              return item.userName === user
                          });

                          if (result) {
                              resolve(
                                  {
                                      name: result.userName,
                                      greetings: result.greetings
                                  }
                              );
                          } else {
                              reject('no greetings');
                          }
                      }
                  }

                  xhr.send();
              })
          }
      }

      const userStorage = new UserStorage();

      const userName = prompt('이름을 입력하세요');
      const password = prompt('비밀번호를 입력하세요');

      userStorage.searchUser(userName, password)
          .then((result) => {
              return userStorage.sayHi(result)
          }).then((result) => {
              alert(`당신에게 인사합니다! ${result.name}${result.greetings}`);
          }).catch((errorMsg) => {
              console.log(new Error(errorMsg));
          });

      // userStorage.searchUser(
      //     userName,
      //     password,
      //     (name) => {
      //         userStorage.sayHi(
      //             name,
      //             (result) => {
      //                 alert(`당신에게 인사합니다! ${ result.name }님 ${ result.greetings }`);
      //             },
      //             (error) => {
      //                 console.log(error);
      //             }
      //         )
      //     },
      //     (error) => {
      //         console.log(error);
      //     }
      // );



  </script>

then도 콜백함수 가지고있고 then이 실행될때 값을 받을 수 있다. ->그게 result
result는 resolve에서 날라온 username이라 userStorage.searchUser().then((result)=>)에 들어옴
userStorage에 접근해서 sayHi에 접근.
sayHi가 반환하는 것은 프로미스 객체다.

promise chaining
searchUser는 프로미스 객체를 반환하는 메소드고 그래서 then 메소드 실행하고 sayHi도 프로미스 객체 반환하기 때문에 then 으로 다시 이어서 반환한다.

<script>
userStorage.searchUser()
.then((result)=>{})
.then((result)=>{alert(`result.name` `result.greentings`)})
.catch(()=>{
});
</script>

실행로직
searchUser->promise객체 반환->promise객체는 then실행시킬수 있음-> then은 promise객체내에서 resolve가 실행되면 실행->안에있는 콜백함수 sayHi메소드 호출->sayHi는 promise 인스턴스(userStorage.sayHi(result))를 반환->resolve가 실행되면 then실행->그럼 위에있는 resolve 객체 name,greetings가 반환(alert문)

fetchAPI

fetch는 promise 안에있는거 = 비동기작동
XMLHttpRequest 대체용!

let result = fetch(" --- ")
console.log(result)
//비동기라 콘솔부터 실행

XMLHttpRequest: onreadystate 조건만족-json으로 받아옴-결과값가공 (갱장히 복잡쓰..)

fetchAPI + then

let result = fetch(" --- ")
console.log(result)
//비동기라 콘솔부터 실행

result.then((data)=>{console.log(data)})

Response라는 객체로 콘솔창에 나타남

옾레) json() 메소드

json()란?
body text(data)를 json으로 파싱한걸 반환한다.
then을 실행한 promise를 반환

let result = fetch('https://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json');
result.then((data) => {
	return data.json();
}).then((result) => {
        return result;
    }).catch((result) => {
        console.log(new Error(result));
    });

result에 data.json() 가 들어오는건가요?!
yes. result에 들어온건 프로미스가 resolve된 상태의 data.json()!

XMLHttpRequest fetch로 바꾸기

<!DOCTYPE html>
<html lang="ko">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <script>
    'use strict';

    class UserStorage {

      // 유저가 등록되었는지 확인
      searchUser(userName, password) {

        return new Promise((resolve, reject) => {
          fetch('users.json')
            .then((data) => {
              return data.json()
            })
            .then((response) => {
              const result = response.user.find((item) => {
                return item.userName === userName && item.password === password
              });

              if (result) {
                resolve(userName);
              } else {
                reject('user not found');
              }
            })
        })
      }

      // 등록된 유저에 따른 인사말을 출력

      // const xhr = new XMLHttpRequest();
      // xhr.open('GET', 'greetings.json');
      // xhr.onreadystatechange = () => {
      //     if (xhr.readyState === 4 && xhr.status === 200) {
      //         console.log(xhr.responseText);
      //         const result = JSON.parse(xhr.responseText).greetings.find((item) => {
      //             return item.userName === user
      //         });

      //         if (result) {
      //             resolve(
      //                 {
      //                     name: result.userName,
      //                     greetings: result.greetings
      //                 }
      //             );
      //         } else {
      //             reject('no greetings');
      //         }
      //     }
      // }

      // xhr.send();
      // const result = fetch('greetings.json')
      // result.then((data) => {
      //   return data.json()
      // })//json은 promise 
      //   .then((result) => { //(1)resolve
      //     return result;
      //   }).catch((result) => { //(2)decline
      //   })


      sayHi(user) {

        return new Promise((resolve, reject) => {

          fetch('greetings.json')
            .then((data) => {
              return data.json();
            }).then((response) => {
              const result = response.greetings.find((item) => {
                return item.userName === user
              });

              if (result) {
                resolve(
                  {
                    name: result.userName,
                    greetings: result.greetings
                  }
                );
              } else {
                reject('no greetings');
              }
            })
        })
      }
    }
    const userStorage = new UserStorage();

    const userName = prompt('이름을 입력하세요');
    const password = prompt('비밀번호를 입력하세요');

    userStorage.searchUser(userName, password)
      .then((result) => {
        return userStorage.sayHi(result)
      }).then((result) => {
        alert(`당신에게 인사합니다! ${result.name}${result.greetings}`);
      }).catch((errorMsg) => {
        console.log(new Error(errorMsg));
      });


    // userStorage.searchUser(
    //     userName,
    //     password,
    //     (name) => {
    //         userStorage.sayHi(
    //             name,
    //             (result) => {
    //                 alert(`당신에게 인사합니다! ${ result.name }님 ${ result.greetings }`);
    //             },
    //             (error) => {
    //                 console.log(error);
    //             }
    //         )
    //     },
    //     (error) => {
    //         console.log(error);
    //     }
    // );



  </script>
</body>

</html>

결과창

async, await

then 없애고 함수화!

제대로 동작안하는 이유
async함수는 비동기적으로 실행되기 때문에 userStorage(async임)를 기다려주지 않으면 값이 undefined될수밖에
그래서 userStorage앞에 await 기재해야함
이제 함수화되었기 때문에->동기적-> const user = userStorage.sayHi()에서 뒷부분 기다려주지않고 alert()부분 실행

const user = await userStorage.sayHi()
//이렇게 바꿔야함

await는 프로미스 객체가 반환될때까지 다음코드 동작 중지하고 프로미스 객체의 fulfilled 값 반환

즉시실행함수의 활용

(function(){})();
(async function(){

    await문
    await문
	alert()
    
})();
try{
(async function(){

    await문
    await문
	alert()
    
})();
}catch(error){
	console.log('no greeting')
}	

0개의 댓글