지난번 callback함수에 대해서 알아보았고 callback지옥을 해결하기위해 promise를 사용한다는 것을 알게 되었다. 이때까지 promise를 공부하며 "왜 promise를 쓰는가?" , "promise는 어떤 상황에 쓰이는가?" 등 promise에 대한 원천적인 개념은 모른체 무작정 new Promise((resolve,reject) => ~~~) 과 .then .catch를 사용하였다.
그렇게 공부하다보니 후에 나온 promise의 syntatic sugar인 "async-await"에 대한 개념을 이해하는데 많은 어려움이 들었다. 확실하게라고는 절대 말할 수 없지만, 이번 시간에 Promise에 대한 근본적 ( ? ) 쓰임에 대해 알고자 한다.
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의 전부라고 말할 수 있다.
비동기처리 같은 경우엔 어떠한 명령을 실행할 때 그 명령이 언제 끝날지 예측하기 어렵거나, 주 가 되는 작업이 아닐때 비동기 처리를 많이 한다. 대표적인 것이 바로 '통신'이다.
서버와 웹브라우저가 데이터를 주고받는 것과 같은 통신을 할 때, 그 통신이 언제 끝나는지 예측하는것이 쉽지가 않다. 인터넷이 느릴 수도 있고 혹은 서버가 느릴 수도 있는 등 통신의 과정에 있어서 수많은 장애가 분명히 존재하기 마련이다.
한 예를 통해 알아보자.
우리는 네이버나 구글같은 포털사이트를 통해 검색창에 무언가를 검색할 때, 검색 자동완성엔진을 경험하게 된다.
이 때, 자동완성 단어 데이터들은 json데이터 타입을 통해 불러오게 되는데 윈도우 검사창 네트워크를 통해 확인해보면 다음과 같다.
Json 데이터들이 나열되어있고 그 데이터들을 첫번째 사진처럼 브라우저에 즉 유저에게 보여주게 된다.
이처럼 브라우저와 웹서버가 페이지 전체를 reload하지 않고도 자바스크립트를 이용해서 서로 통신을 하여 웹 페이지의 일부분만을 갱신하는 것을"Ajax"라고 한다. ( Ajax = Asynchronous JavaScript and XML )
이번에 다룰 내용은 "Promise"이므로 Ajax나 Json에 대해선 깊게 파헤치지는 않겠다. 이번 포스팅에서는 ajax와 같이 서버에서 데이터를 불러오는데에 있어 "Promise"가 비동기처리로 사용된다는 핵심만 인지하고 있자.
이렇게 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( ) method 의 Syntax를 알아보자.
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하게 코드가 처리된다.
- fetch함수가 return한 값은 then과 catch , 이 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에 대해 자세히 알아보자.
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를 어떻게 비동기적인 코드로 작성하느냐 에 대해 작성해 보겠다.