본문 바로가기
Develop/백엔드 & 서버

Javascript는 비동기 함수를 어떻게 처리해 - 프로미스

by 구운밤이다 2021. 1. 20.
728x90
반응형

시리즈 목차

이 글은 시리즈 글입니다. 이번 글에서는 자바스크립트에서 비동기 작업을 더 쉽게 다룰 수 있는 방식인 Promise에 대해 알아볼 것입니다. 기본적으로 자바스크립트가 비동기로 동작하지 않는다는 사실과 비동기룰 다루던 첫 형태인 콜백함수는 저번 글에서 다루었습니다! 자바스크립트가 비동기함수를 어떻게 처리하는지 동작 원리를 모르시는 분과 콜백함수가 뭔지 잘 모르겠다 하시는 분들은 이전 글을 참고해보세요!

Promise

프로미스가 뭘까요? 프로미스는 약속을 뜻하는 단어이죠! 프로미스를 사용하면 비동기 메소드에서 마치 동기 (일반) 메소드 처럼 값을 반환할 수 있습니다. 하지만 실제 값을 반환하지는 않고 프로미스 객체를 반환해서 미래에 값이 반환되는 시점에 결과를 제공합니다. 앞에서 설명한 것처럼, 자바스크립트에서 프로미스는 비동기 처리에 사용되는 객체인데 MDN에서 프로미스의 정의를 보면 다음과 같습니다.

Promise 객체는 비동기 작업이 맞이할 미래의 완료 또는 실패와 그 결과 값을 나타냅니다.

위 정의에서 알 수 있듯이 비동기 작업을 할 때 가질 수 있는 값을 나타내줍니다. 프로미스는 이를 상태를 통해 표현하는데요. 프로미스가 가질 수 있는 상태는 아래와 같습니다.

  • 대기(pending): 이행되거나 거부되지 않은 초기상태.
  • 이행(fulfilled): 연산이 성공적으로 완료.
  • 거부(rejected): 연산이 실패.

Then, catch, Finally

대기 중인 프로미스를 실행하다가 오류가 나면 rejected 상태로 가고, 잘 실행된다면 이행 상태로 갈 수 있습니다. 이 프로미스는 then, catch, finally 메소드로 연결할 수 있습니다. 위 이미지는 프로미스 상태가 변하면서 처리하는 과정을 나타냅니다. 프로미스를 잘 이행해 결과값을 받으면 처리해 줄 수 있는 then 메소드, 실패한 이유를 받을 수 있는 catch 메소드를 통해 처리합니다. 마지막으로 finally 메소드로 추가 작업을 해줄 수 있는데요. 이 메소드는 프로미스가 처리된 후(이행 혹은 거부된 후) 실행되어 원하는 처리를 해줄 수 있습니다.

예제

let myFirstPromise = new Promise((resolve, reject) => {
  setTimeout(function(){
    resolve("Success!"); // Yay! Everything went well!
  }, 250);
});

myFirstPromise.then((successMessage) => {
  console.log("Yay! " + successMessage);
});

예제를 통해 공부해보죠! myFirstPromise를 통해 새로운 프로미스 인스턴스를 생성했습니다. 만약 프로미스 객체가 잘 이행되면 resolve 메소드를 호출하고, 거부된다면 reject 메소드를 호출합니다. 위에서는 setTimeout 단골 비동기 예제를 사용했구요. 성공한다면 success! 라는 메세지를 resolve에 담아 프로미스 객체로 던집니다. resolve를 실행하면 이행 상태로 넘어가고 이행 상태가 되었으므로 then 메소드를 호출합니다. 결과적으로 콘솔에는 0.25초 이후 "Yay! Success!" 메세지가 나오게 되겠죠!

fetch("https://donologue.tistory.com/posts/1")
  .then((response) => console.log("response:", response))
  .catch((error) => console.log("error:", error))

보통 비동기 작업 후 결과를 받았는데 에러가 나면 처리를 해줘야겠죠? 따라서 이런식으로 resolve 이면 이행 상태이므로 then 메소드로 프로미스 객체가 갈테고, catch 메소드로 거부 상태이면 에러 결과를 핸들링해주는 부분을 추가해 사용할 수 있습니다. 이런식으로 프로미스는 동기 작업에서의 try catch 블럭과 유사하게 then catch 메소드르 이용해 비동기 처리 코드를 작성할 수 있게 해줍니다.

프로미스의 메소드

조금 전에 resolve와 reject라는 용어가 나왔는데요. 프로미스의 메소드에는 all, race, reject, resolve 가 있습니다. 여러 프로미스를 묶을 때 사용하는 all은 알아두면 좋습니다. 아래에서 조금 더 자세히 다루기로 하고, 나머지는 간단히 설명하자면 race 메소드는 Promise.race(iterable) 이런 식으로 사용될 때 iterable 안에서 어떤 프로미스가 이행하거나 거부하는 즉시 스스로 이행하거나 거부할 수 있습니다. 주어진 이유로 거부할 수 있는 프로미스 객체를 반환하는 reject, 주어진 값으로 이행하는 프로미스 객체를 반환하는 resolve 메소드는 이정도로 간단히 설명하고 넘어갈게요!

Promise.all(iterable)에 all이란 이름이 붙은 이유는 iterable 안의 모든 프로미스를 이행한 뒤 이행하고(fulfilled), 내부에 한 프로미스가 reject되면 바로 거부하는 프로미스를 반환합니다. 반환된 프로미스가 이행되면 iterable 내의 모든 프로미스가 이행 상태인 거죠. 이 메소드는 비슷한 기능 혹은 여러 응답을 필요로 하는 어떤 한 기능에 프로미스를 묶을 때 사용하면 편리하고 코드도 간결해집니다.

그런데 then, catch, finally는 왜 여기서 설명 안하고 따로 앞에서 먼저 설명했을까요? 이들이 중요해서 먼저 설명한 것도 있지만, 정확히는 이들은 프로미스 인스턴스의 프로토타입에 속한 메소드이기 때문입니다.

프로토타입이란?

프로토타입은 다른 언어에서 클래스와 비슷한 역할을 합니다. 클래스는 객체지향언어에서 빠질 수 없는 중요한 개념이죠. 자바스크립트도 객체지향 언어인>데 프로토타입을 통해 상속을 흉내낼 수 있습니다. 최근 ECMA6 표준에서는 Class 문법이 추가되었긴 하지만, 자바스크립트는 여전히 프로토타입 기반 언어입니다.

프로미스 연결 (chaining)

프로미스는 이행하거나 거부된 상태로 가면 resolve()나 reject() 메소드를 통해 프로미스 객체를 리턴합니다. 이를 then()이나 catch() 메소드를 통해 처리할 수 있는데요. then()과 catch() 메소드 또한 프로미스 객체를 리턴하는데요. 이는 then() 메소드와 catch() 메소드가 체이닝 될 수 있음을 암시합니다. 연쇄적으로 then 메소드를 호출하여 여러 처리가 가능해진 것이죠.

체이닝 예제

fetch("https://donologue.tistory.com/posts/1")
  .then((response) => response.json())
  .then((post) => console.log("post:", post))
  .catch((error) => console.log("error:", error))

위 체이닝 예제를 봅시다. 위는 fetch 함수를 통해 응답을 받는데 성공하면 그 응답을 json으로 파싱합니다. json 파싱에 성공하면 그 json을 콘솔에 나타내는 거죠. 위 예제가 실제로 동작하지는 않겠지만 만약 여기까지 성공한다면 그 결과는 post: {title: "Javascript는 비동기 함수를 어떻게 처리해 ... } 이런 식으로 나오겠죠? 만약 여기서 fetch 작업이 오류가 났던지, 응답을 json으로 파싱하는 데 오류가 발생해 거부 상태로 가게되면 그 프로미스 객체는 catch() 메소드에 들어가 error: ... 이런 식으로 로그가 나오게 될 겁니다.

마치며

이렇게 프로미스에 대해 알아보았는데요. 이전 글에서 알아보았던 콜백함수보다 훨씬 보기도 깔끔하고 이해도 더 잘되는 코드이죠? 아직도 사용되는 메소드들이 많기도 합니다.(Promise.all, then, catch 방식.. 등) 최근에는 async await 방식을 더 많이 사용하고 있는 추세이지만요. 이 시리즈의 마지막 글인 다음 글에서는 async await 이 어떻게 동작하는지 알아보도록 하겠습니다.


참고 - Promise - JavaScript - MDN - Mozilla

728x90
반응형

댓글