Promise

chris0205.eth·2022년 2월 19일
0

JS 기초

목록 보기
1/2

들어가며

JavaScript 세계에서는 거의 대부분의 작업들이 비동기(asynchronous)로 이뤄진다. 어떤 작업을 요청하면서 콜백 함수를 등록하면 작업이 수행되고 나서 결과를 나중에 콜백 함수를 통해 알려주는 식이다.

Synchronous vs. Asynchronous


Synchronous(동기) 방식은 한가지 요청이 완료되고 나면 다음 요청을 시행한다.
Asynchronous(비동기) 방식은 응답에 관계없이 바로 다음 요청을 시행한다. 통신이 필요한 곳에 많이 이용된다.

ajax

Asynchronous JavaScript and XML
비동기적인 웹 애플리케이션의 제작을 위해 아래와 같은 조합을 이용하는 웹 개발 기법이다.

  • 표현 정보를 위한 HTML(또는 XHTML)과 CSS
  • 동적인 화면 출력 및 표시 정보와의 상호작용을 위한 DOM, 자바스크립트
  • 웹 서버와 비동기적으로 데이터를 교환하고 조작하기 위한 XML, XSLT, XMLHttpRequest (Ajax 애플리케이션은 XML/XSLT 대신 미리 정의된 HTML이나 일반 텍스트, JSON, JSON-RPC를 이용할 수 있다).

    페이지 리로드 없이 JS이용해(웹서버의 응답을 처리하기 위해 클라이언트쪽에서 자바스크립트를 사용한다.) 통신할 수 있다.

Promise

JavaScript 자체는 동기적(synchronous), blocking, single-threaded 언어이다.
이러한 JavaScript가 비동기적으로 동작하도록 하는 몇 가지 방법이 있다. 콜백함수 이용하기, Promise, 그리고 async-await을 이용하는 것이다. 그 중에서 Promise를 먼저 알아보겠다.

비유

유명한 가수가 앨범을 언제 내는지에 대한 소식을 원하는 팬들을 위해 구독리스트를 만들었다. 그리곤 그 리스트를 팬들에게 전달해 이메일 주소를 적게 하고, 앨범이 준비되면 리스트에 있는 팬들에게 메일을 보내 앨범 관련 소식을 바로 받아볼 수 있게 했다.

이 비유는 코딩을 하면서 아래 같은 상황과 유사하다.

  1. 제작코드(producing code)는 원격에선 스크립트를 불러오는 것과 같은 시간이 걸리는 일을 한다. 위 비유에선 '가수'가 제작코드에 해당한다.
  2. 소비코드(consuming code)는 제작코드의 결과를 기다렸다가 이를 소비한다. 이때 소비 주체(함수)는 여럿이 될 수 있다. 위 비유에서 소비코드는 '팬'이다.
  3. promise는 제작코드와 소비코드를 연결해주는 특별한 자바스크립트 객체이다. 위 비유에서 promise는 '구독리스트'이다. promies는 시간이 얼마나 걸리든 상관없이 약속한 결과를 만들어내는 제작코드가 준비되었을때, 모든 소비코드가 결과를 사용할 수 있도록 해준다.

new Promise

promise 객체는 아래와 같은 문법으로 만들 수 있다.

let promise = new Promise(function(resolve, reject) {
  // executor (제작 코드, '가수')
});

executor의 parameter인 resolve와 reject는 자바스크립트에서 자체 제공하는 콜백이다. 개발자는 resolve와 reject를 신경 쓰지 않고 executor 안 코드만 작성하면 된다. 이때, executor는 자동으로 실행된다.
대신 executor에선 결과를 즉시 얻든 늦게 얻든 상관없이 상황에 따라 parameter로 넘겨준 콜백 중 하나를 반드시 호출해야 한다.

  • resolve(value) — 일이 성공적으로 끝난 경우 그 결과를 나타내는 value와 함께 호출
  • reject(error) — 에러 발생 시 에러 객체를 나타내는 error와 함께 호출


한편, promise 객체는 state(pending/fulfilled/rejected)result(undefined/done/error) 같은 '내부 property(개발자가 직접 접근할 수 없다.)'를 가진다.

간단하게 일정 시간 후에 resolve와 reject 콜백을 호출하는 executor를 가진 promise를 만들었다.

let promise = new Promise(function(resolve, reject) {
  // 프라미스가 만들어지면 executor 함수는 자동으로 실행됩니다.

  // 1초 뒤에 일이 성공적으로 끝났다는 신호가 전달되면서 result는 'done'이 됩니다.
  setTimeout(() => resolve("done"), 1000);
});
let promise = new Promise(function(resolve, reject) {
  // 1초 뒤에 에러와 함께 실행이 종료되었다는 신호를 보냅니다.
  setTimeout(() => reject(new Error("에러 발생!")), 1000);
});

using promise

프라미스 객체는 executor(‘제작 코드’ 혹은 ‘가수’)와 결과나 에러를 받을 소비 함수(‘팬’)를 이어주는 역할을 한다. 소비함수는 .then, .catch, .finally 메서드를 사용해 등록(구독)된다. 소비함수들을 통해 내부 property에 접근이 가능하다!!

.then

promise.then(
  function(result) { /* 결과(result)를 다룹니다 */ },
  function(error) { /* 에러(error)를 다룹니다 */ }
);

첫번째 인수는 promise가 이행되었을때 실행되는 함수고, 여기서 실행결과를 받는다. 두번째 인수는 promise가 거부되었을때 실행되는 함수고, 여기서 에러를 받는다.
만약 작업이 성공적으로 처리된 경우만 다루고 싶다면 .then에 인수를 하나만 전달하면 된다.

.catch

.catch(fn)는 문법이 간결하다는 점만 빼고 .then(null,fn)과 완벽하게 같다.

.finally

프라미스가 처리되면(이행이나 거부) f가 항상 실행된다는 점에서 .finally(f) 호출은 .then(f, f)과 유사하다. 쓸모가 없어진 로딩 인디케이터(loading indicator)를 멈추는 경우같이, 결과가 어떻든 마무리가 필요하면 finally가 유용하다.

new Promise((resolve, reject) => {
  /* 시간이 걸리는 어떤 일을 수행하고, 그 후 resolve, reject를 호출함 */
})
  // 성공·실패 여부와 상관없이 프라미스가 처리되면 실행됨
  .finally(() => 로딩 인디케이터 중지)
  .then(result => result와 err 보여줌 => error 보여줌)

위와 같이 사용한다.

그런데, finally는 then(f, f)과 완전히 같진 않다. 차이점은 다음과 같다.

  1. finally 핸들러엔 인수가 없다.
  2. finally 핸들러는 자동으로 다음 핸들러에 결과와 에러를 전달한다.
    finally는 프로미스 결과를 처리하기 위해 만들어진 것이 아니라서 프로미스 결과는 finally를 통과해서 전달된다. 이런 특징은 아주 유용하게 사용되기도 한다.
  3. .finally(f)는 함수 f를 중복해서 쓸 필요가 없기 때문에 .then(f, f)보다 문법 측면에서 더 편리하다.

마무리

promise를 이용하면 콜백지옥을 피할 수 있게 된다. promise chaining과 async-await 등은 다음에 한 번 다뤄보도록 하겠다.

참고

Is JavaScript Synchronous or Asynchronous?
Asynchronous JavaScript
Promise-basic

profile
long life, long goal

0개의 댓글