Promise

Mono crom·2021년 2월 1일
0

1. Intro : 콜백 지옥(Callback Hell)

console.log(1);
setTimeout(() => console.log(2), 0);
console.log(3);

// 실행결과 : 1 3 2

setTimeout, addEventListener 메서드의 작업은 대표적인 비동기 처리의 예이다. 위의 코드 에서 js는 콘솔에 1을 찍고, setTimeout 함수는 0초 뒤에 화살표함수를 실행하도록 예약을 건 뒤, 콘솔에 3을 찍는다. 이렇게 스택이 모두 해소되고 나면 비로소 예약걸려있던 화살표함수가 스택에 올라오면서 콘솔에 2가 찍힌다.

비동기는 처리방식은 유용하지만 때로는 순서대로 실행되는게 더 중요한 경우도 있을 것이고, ES6 이전에 setTimeout 메서드를 사용하면서도 순차적으로 실행되게끔 하기 위해서는 아래와 같은 중첩구조를 사용해야 했다.

function count(callback) {
  setTimeout(() => callback(), 1000);
}

count(() => {
  console.log(1);
  count(() => {
    console.log(2);
    count(() => {
      console.log(3);
    });
  });
});

// 실행결과 : 1 2 3

위처럼 중첩구조를 이용하면 비동기 처리의 실행 순서를 제어할 수 있다. 그러나 처리할 콜백 함수의 중첩이 10번만 이루어져도 코드들이 켜켜이 쌓여 내용을 이해하는 것이 굉장히 어려워질 것이다. 이렇게 미친듯이 콜백 함수가 중첩되어 읽는이로 하여금 쌍욕이 튀어나오게 하는 경우를 일컬어 콜백지옥 이라 한다.

ES6 이후로는 promise 를 사용해 이러한 문제를 해결하고 비동기 처리도 간결하게 작성할 수 있게 되었는 바, 이하에서 소개한다.


2. Promise 기본

Promise 는 비동기 처리를 실행하고, 그 처리가 끝난 후 다음 처리를 실행하기 위해 사용된다. 이는 Promise 인스턴스를 만드는 것으로부터 시작되며, 그 기본적인 문법은 아래와 같다.

let promise = new Promise((resolve, reject) => { ... });

화살표 함수에 작성된 처리가 끝나면 resolve 함수가 호출되고, 처리에 실패하면 reject 함수가 호출된다. resolve 함수든 reject 함수든 호출된다는 것은 곧 Promise 함수의 종료를 의미한다. 특히 resolve 함수가 호출되면 뒤에 딸린 .then 메소드의 함수가 호출된다. 글보다 아래 예를 보면서 이해하자.

let promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log(1);
    resolve();
  }, 1000);
});

promise.then(() => {
  console.log(2);
})

// 실행결과 : 1 2

주목할 부분은 4번째 줄의 resolve() 이다. 1초 뒤에 1을 콘솔에 찍고, resolve함수를 호출해서 함수를 종료시킨다. resolve 함수가 실행되면서 then 메서드에 정의한 함수가 호출되면서 2가 콘솔에 찍힌다.


3. resolve 함수와 then 메서드

상술한 것처럼 resolve 함수는 Promise를 종료시키며, 또한 resolve 함수에 인수로 전달한 값은 이후 then 메서드에 전달되어 사용된다. 아래 예시를 보자.

let promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    let name = prompt("write your name!");
    resolve(name);
  }, 1000);
});

promise.then( (name) => {
  console.log("hello, " + name + "!");
});

// 실행결과 : 프롬프트에 crom 입력 -> hello, crom!

위 코드가 실행되면 먼저 "write your name!" 프롬프트를 통해 이름을 묻고, 뭔가를 입력하면 그 값이 변수 name 에 할당된다. 그리고 resolve 함수를 호출함으로서 Promise 함수를 종료시키고, 그 다음 처리를 위한 인자로서 내가 입력한 값을 담은 변수 name 을 넘긴다.

그러면 then 메서드로 넘어와서, 인자 name 에 좀 전 Promise 함수를 통해 넘어온 변수 name이 일련의 문자열과 함께 콘솔에 찍히면서 모든 처리가 끝난다.


4. reject 함수와 catch 메서드

resolve 함수와 마찬가지로 reject 함수도 Promise를 종료시킨다. 또 resolve 함수와 마찬가지로 reject 함수에도 특정 값을 넘길 수 있다. resolve와 다른 것은, reject 함수가 호출되면 then 메서드가 아니라 catch 메서드에 정의한 함수가 실행된다.

let promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    let n = parseInt(prompt("write number that smaller than 10"));
    
    // 프롬프트에 입력된 값에 따라 resolve 또는 reject 함수가 호출된다.
    // 전자의 경우 then 메서드에 n이, 후자의 경우 catch 메서드에 에러 문자열이 전달된다.
    if (n <= 10) {
      resolve(n);
    } else {
      reject(`error : ${n} is bigger than 10`);
    }
  }, 1000);
});

promise
  // resolve 함수가 호출된 경우 then 메서드로 넘어옴
  .then( num => {
    console.log(`2^${num} = ${Math.pow(2, num)}`);
  })

  // reject 함수가 호출된 경우 catch 메서드로 넘어옴
  .catch( error => {
    console.log(error);
  });

// 실행결과 : 프롬프트에 3 입력 -> 2^3 = 8
// 실행결과 : 프롬프트에 12 입력 -> 12 is bigger than 10

5. then 메서드 하나로 reject까지 해결하기

then 메서드는 사실 인자를 두 개 받을 수 있다. 첫 번째 인자는 성공 콜백함수, 두 번째 인자는 실패 콜백함수. 그러면 catch 메서드 없이도 then 메서드만으로 실패 시 처리도 해결할 수 있다.

promise
  .then( num => {
    console.log(`2^${num} = ${Math.pow(2, num)}`);
  })
  .catch( error => {
    console.log(error);
  });

// 위에서 작성했던 함수를 아래와 같이 수정할 수 있다.

promise.then {
  // 처리 성공 시 호출되는 함수
  (num) => {
    console.log(`2^${num} = ${Math.pow(2, num)}`);
  },
    
  // 처리 실패 시 호출되는 함수
  (error) => {
    console.log(error);
  }
profile
니가 진짜로 원하는게 뭐야

0개의 댓글