Promise와 비동기 처리

DatQueue·2022년 3월 11일
2

Promise 시작하기

지난번 callback함수에 대해서 알아보았고 callback지옥을 해결하기위해 promise를 사용한다는 것을 알게 되었다. 이때까지 promise를 공부하며 "왜 promise를 쓰는가?" , "promise는 어떤 상황에 쓰이는가?" 등 promise에 대한 원천적인 개념은 모른체 무작정 new Promise((resolve,reject) => ~~~) 과 .then .catch를 사용하였다.
그렇게 공부하다보니 후에 나온 promise의 syntatic sugar인 "async-await"에 대한 개념을 이해하는데 많은 어려움이 들었다. 확실하게라고는 절대 말할 수 없지만, 이번 시간에 Promise에 대한 근본적 ( ? ) 쓰임에 대해 알고자 한다.

Synchronous(동기) vs Asynchronous(비동기)

synchronous는 말 그대로 순차적으로 진행하는 코드이다.

console.log(1);
console.log(2);
console.log(3);
console.log(4);

콘솔창에서 결과를 확인해보면 1,2,3,4 이렇게 순서대로 코드가 진행될 것이다.

그러면 위 코드중 일부를 비동기처리 해보자.

console.log(1);
console.log(2);
setTimeout(()=>{console.log(3)},5000);
consoole.log(4);

이런식으로 console.log(3)을 setTimeout을 통해 비동기처리를 해 본 결과 1,2,4가 먼저 출력되고 5초 뒤 3이 출력되는 것을 알 수 있다.
위의 예시는 단순한 것을 넘어선 유치원 수준의 코드지만 사실 저것이 synchronous와 asynchronous의 전부라고 말할 수 있다.

Asynchronous(비동기 처리)는 어떨때 사용하는가?

비동기처리 같은 경우엔 어떠한 명령을 실행할 때 그 명령이 언제 끝날지 예측하기 어렵거나, 주 가 되는 작업이 아닐때 비동기 처리를 많이 한다. 대표적인 것이 바로 '통신'이다.
서버와 웹브라우저가 데이터를 주고받는 것과 같은 통신을 할 때, 그 통신이 언제 끝나는지 예측하는것이 쉽지가 않다. 인터넷이 느릴 수도 있고 혹은 서버가 느릴 수도 있는 등 통신의 과정에 있어서 수많은 장애가 분명히 존재하기 마련이다.

한 예를 통해 알아보자.
우리는 네이버나 구글같은 포털사이트를 통해 검색창에 무언가를 검색할 때, 검색 자동완성엔진을 경험하게 된다.

이 때, 자동완성 단어 데이터들은 json데이터 타입을 통해 불러오게 되는데 윈도우 검사창 네트워크를 통해 확인해보면 다음과 같다.

Json 데이터들이 나열되어있고 그 데이터들을 첫번째 사진처럼 브라우저에 즉 유저에게 보여주게 된다.
이처럼 브라우저와 웹서버가 페이지 전체를 reload하지 않고도 자바스크립트를 이용해서 서로 통신을 하여 웹 페이지의 일부분만을 갱신하는 것을"Ajax"라고 한다. ( Ajax = Asynchronous JavaScript and XML )

이번에 다룰 내용은 "Promise"이므로 Ajax나 Json에 대해선 깊게 파헤치지는 않겠다. 이번 포스팅에서는 ajax와 같이 서버에서 데이터를 불러오는데에 있어 "Promise"가 비동기처리로 사용된다는 핵심만 인지하고 있자.

fetch( ) - Web APIs 를 통해 알아보는 비동기처리

이렇게 Json 객체를 통해 데이터를 뽑아오는데 쓰는 자바스크립트 API로 fetch API가 있다. ( 여기서는 깊게 다루지는 않는다) 아래의 예시를 보자.

간단하게만 코드의 진행을 보자면 먼저 주소를 받고 myJson이라는 parameter를 통해서 웹서버가 리턴해준 Json data-type을 자바스크립트의 data-type에 맞는 결과로 converting해준다.
위 예제는 MDN의 공식예제이고 저 주소창엔 아무런 정보가 없으므로 변형시킨 새로운 예제를 살펴보자.

fetch("https://jsonplaceholder.typicode.com/posts")
  .then(function (response) {
    return response.json();
  })
  .then(function (myJson) {
    console.log(myJson);
  });

Jsonplaceholder 사이트를 통해서 json data-type의 객체와 그 url을 뽑아왔다.
Jsonplaceholder - 링크

살펴보면 다음과 같다.

그리고 우리가 작성한 코드를 실행한 결과를 보면

다음과 같다.

Json data-type으로 된 text를 위 fetch를 이용한 코드를 통해서 자바스크립트의 data-type으로 바꾸었다.우리는 이렇게 불러온 데이터들을 통해 다양한 작업을 수행할 수 있는 것이다.
그리고 이러한 fetch의 코드진행은 "비동기"로 진행된다.

예시를 보자.

console.log(1);
fetch("https://jsonplaceholder.typicode.com/posts")
  .then(function (response) {
    return response.json();
  })
  .then(function (myJson) {
    console.log(myJson);
  });
console.log(2);

기존 코드에서 위 아래로 1과 2를 호출하는 코드를 실행시켜보았다.

보는 것처럼 1과 2가 먼저 실행이되었고 콜백함수의 결과는 나중에 나오게 되었다.

이러한 결과가 일어나는 것을 알기에 앞서 Mdn에 나와있는 fetch( ) methodSyntax를 알아보자.

MDN-fetch( ) API

const fetchResponsePromise = fetch(resource [, init]);

여기서 parameter의 첫번째 인자인 resource로 url을 주게되고 어떠한 값을 return하게 될 것인데 어떤 값을 return 하게 되냐면 이것 또한 Mdn에 이렇게 정의되어있다.

보다시피 "Promise형태로 된 값을 return하게 되고 그 Promise data-type은 Response object를 돌려줄 것 " 이라고 말한다.

다음 코드를 보면 알 수 있다.

let fetched = fetch("https://jsonplaceholder.typicode.com/posts");
console.log("fetched", fetched);

콘솔창을 확인해보면

위와 같이 fetch( )는 return값으로 Promise를 반환한 것을 알 수 있다.
또한 이러한 Promise는 거의 대부분 asynchronous하게 코드가 처리된다.

.then( ) , .catch( )

  • fetch함수가 return한 값은 thencatch , 이 2개의 method(함수)를 수행하게 된다.
  • then과 catch는 둘 다 callback 함수를 받고, 각 각 하나의 parameter를 가진다.

다음 예제를 살펴보자.

let fetched = fetch("https://jsonplaceholder.typicode.com/posts");
console.log("fetched", fetched);
fetched.then(function (result) {
  console.log("result", result);
});
fetched.catch(function (reason) {});

앞전의 예시에 then과 catch 메서드를 추가하였다. 그럼 콘솔 창의 결과를 확인해보자.

then 메서드 안의 callback함수 parameter인 result를 확인해보니 Response 라고 표기되는 것을 알 수 있다. 이제 위에 나온 MDN에서 정의한 return value값에 대한 설명이 이해가 갈 것이다.

"A Promise that resolves to a Response object." ( resolve : 성공적으로 실행이 됨을 의미)

해석해보자면 "Promise는 성공적으로 실행이 되었을 때 Response object (객체) 를 반환한다." 이렇게 말할 수 있다.

즉, 위의 예제는 fetch ( )함수를 통한 데이터를 불러오는 통신에 성공한 사례이므로 then ( ) 메서드안의 callback함수가 실행이된다.

그렇다면 일부러 코드에 오류를 내보자 !

다음은 url을 잘 못 적음으로써 통신의 오류를 발생시켜 본 케이스다.

let fetched = fetch("https://jsonplaceholder1111.typicode.com/posts");//url 오류
console.log("fetched", fetched);
fetched.then(function (result) {
  console.log("result", result);
});
fetched.catch(function (reason) {
  console.log("reason", reason);
});

결과를 확인해보자.


console창에 이번엔 result가 나오지 않고, reason이 호출되면서 결과 값으로는 fetch에 실패하였다는 TypeError가 반환되었다.
이를 통해 fetch를 통한 통신에 실패하였을 경우 catch( ) 메서드의 callback이 실행된다는 것을 확인 할 수 있다.

우린, 간단하게 fetch API와 그 methods인 then과 catch의 진행 과정 또한 알아보았다.

사실 위 예시처럼 코드를 짜지는 않는다. 더 간략하게 코드를 짤 수 있다.

fetch("https://jsonplaceholder1111.typicode.com/posts");
  .then(function (result) {
    console.log("result", result);
  });
  .catch(function (reason) {
    console.log("reason", reason);
  });

이렇게 따로 변수 설정없이 fetch로 resource를 받고 바로 then이나 catch method로 진행하여주면 된다. then메서드안의 callback함수 또한 Promise를 반환하기 때문에 그 뒤의 then이나 catch메소드를 연결시켜 진행할때 또한, 바로 .then 혹은 .catch로 작성하여주면 된다.

흔히, 우리는 이것을 "Promise chaining"이라고 한다.

Promise chaining

다음 예시를 통해 Promise chaining에 대해 자세히 알아보자.

fetch("https://jsonplaceholder.typicode.com/posts")
  .then(function (response) { //이해하기 쉽게 result -> response
    response.json().then(function (data) {
      console.log("data", data);
    });
  })
  .catch(function (reason) {
    console.log("reason", reason);
  });

제대로 된 url을 통해 data를 뽑아오는데 성공하였으므로 then 메서드가 실행된다.
그 때, then의 callback함수 parameter인 즉, resolve된 값인 response는 아직까진 json data-type을 유지하고 있다. 아직 텍스트 상태임에 불과하다.
이제 우리는 이 response에 json( ) 이라는 메서드를 붙여 웹 브라우저에게 이 텍스트가 json data-type임을 알려준다.

자, 이제 response 객체는 또다시 then 메서드를 받을 수 있고 json ( )메서드를 통해 자바스크립트의 data-type으로 converting된 json 텍스트는 두 번째 then의 callback parameter인 "data"를 통해 return된다.

참고

fetch로는 바로 데이터를 사용할 수 없다.
fetch를 사용할 땐 두 단계를 거쳐야 한다. 먼저 올바른 url로 요청을 보내야 하고, 바로 뒤에오는 응답에 대해 json()을 해줘야 하는 것이다
json()은 Response 스트림을 가져와 스트림이 완료될때까지 읽는다. 그리고 다 읽은 body의 텍스트를 Promise형태로 반환한다.

그럼 위 코드의 결과를 확인해보자. 결과를 확인해보기위해 response.json 의 then 메서드안에 callback parameter값인 "data"를 출력해보았다.

가장 처음에 보았던 예제 (위로 올라가서 확인) 와 같이 javascript data-type으로 변경되어 나온 것을 확인 할 수 있다.

이렇게 Promise를 중심으로 then안에 then이 들어갈 수 있고, 또 그 then안에 then이 들어갈 수 있는 것이다. 이것을 우리는 "Nested promise" 라고도 부른다.

사실, 위에 처럼 코드를 짜는 경우는 거의 드물다. 저 코드를 조금 더 실용적으로 작성해보자면

fetch("https://jsonplaceholder.typicode.com/posts")
  .then(function (response) {
    // response.json().then(function (data) {
    //   console.log("data", data);
    // });
    return response.json();
  })
  .catch(function (reason) {
    console.log("reason", reason);
  })
  .then(function (data) {
    console.log("data", data);
  });

이처럼 만약에 url을 제대로 불러왔다면 then 메서드를 통해 response객체를 받아오게 되고 그 response객체에 json메서드를 붙여 return한다. (통신을 성공했다는 가정이므로 catch는 무시)

위에 "참고" 에 적혀져있다시피 json()은 Response 스트림을 가져와 스트림이 완료될때까지 읽는다. 그리고 다 읽은 body의 텍스트를 Promise형태로 반환한다.

즉, return된 response.json( ) 은 Promise이고 Promise는 또 다시 then( ) 을 호출시켜 연결할 수 있다. 코드 밑에 줄에서 알다시피 또 다시 then( ) 을 사용해 response의 data를 불러온 것을 알 수 있다.

실행결과는 확인해 볼 필요도 없이 앞전의 결과와 같다.

앞전의 진행방식이 "Nested promise"라고 했다면 이렇게 then안에서 Promise를 return하고 해당 then메서드 밖에서 또 다시 then메서드를 실행해 Promise값들을 연결시켜주는 것을

바로 "Promise Chaining" 이라고 하는 것이다.
(일반적으로 선호되는 방식)

마무리...

설명이 장황하였지만 지금까지는 Promise의 시작( ? )에 불구하다. 이번 포스팅은 Promise의 비동기 처리 및 작성방식에 대한 정리가 아닌 어떻게보면 소비자의 입장에서 Promise를 어떻게 받아들일 것인가에 대한 해석이다. 즉, Promise는 어디에 쓰이고, 왜 쓰이느냐에 대해 간단히 알아본 것이다. 가장 서두에 언급했다시피 이러한 근본적인 쓰임을 모르는 나로써는 앞으로의 Promise와 async-await과 같은 방식에 대해 이해하기가 난해할 것임에 틀림없기 때문이다.

그럼 다음 포스팅으로 Promise를 어떻게 비동기적인 코드로 작성하느냐 에 대해 작성해 보겠다.

profile
You better cool it off before you burn it out / 티스토리(Kotlin, Android): https://nemoo-dev.tistory.com

0개의 댓글