본문 바로가기

Javascript/이론+예제

[ES6] 비동기 함수 (Promise)

 

Do it! 리액트 프로그래밍 정석 책과 다른 책을 참고하며 공부한 내용을 정리하는 글입니다.

 


동기적 처리

작업을 요청함과 동시에 작업의 결과를 그 자리에서 받을 수 있는 데이터 처리 방식

 

비동기 처리

작업을 요청하지만 결과는 그 자리에서 꼭 받지 않아도되는 데이터 처리 방식

 

비동기 처리의 대표적인 예

서버에 데이터를 요청하고 결과를 기다리는 과정
- 서버에 데이터를 요청하는 동안 다른 작업을 진행하다가 데이터 요청이 완료되면 본격적으로 작업을 진행

 

자바스크립트의 비동기 처리

- 자바스크립트의 엔진은 싱글 스레드로 이루어져 있어, 동시에 두가지 작업을 할 수 없다
- 자바스크립트의 엔진은 비동기 처리가 가능하도록 설계되어있음
- 특정 코드의 실행이 완료될 때까지 기다리지 않고 다음 코드를 먼저 수행하는 자바스크립트의 특성

 

카페에서 커피를 주문할 때, 앞 사람이 주문을 하고 주문한 커피를 다 제공한 다음, 다음 사람의 주문을 받는 것을 동기적 처리
모든 사람의 주문을 한꺼번에 받고 커피가 완성되는 대로 사람들에게 커피를 제공한다면 비동기적 처리

2022.10.22 - [Javascript/이론+예제] - 자바스크립트의 동작 원리


기존 자바스크립트의 비동기 함수 처리 방법

기존 자바스크립트는 비동기 작업을 위해 지연 작업이 필요한 함수에 setTimeout() 함수를 이용했음
지연 작업 완료 이후에 실행되어야 하는 함수는 지연 작업 함수의 인자 (콜백함수) 를 전달하여 처리했음

 

function work1(onDone) {
  setTimeout(() => onDone('작업1 완료!'), 100);
}
function work2(onDone) {
  setTimeout(() => onDone('작업1 완료!'), 200);
}
function work3(onDone) {
  setTimeout(() => onDone('작업3 완료!'), 300);
}
function urgentWork() {
  console.log('긴급 작업');
} // 지연 작업과 상관없이 바로 실행되어야 하는 함수

work1(function(msg1) {
  console.log('done after 100ms:' + msg1);
  work2(function(msg2) {
    console.log('done after 300ms:' + msg2);
    work3(function(msg3) {
      console.log('done after 600ms:' + msg3);
    });
  });
});
urgentWork();

작업 된 모습

  • 콜백함수가 총 3개의 계단 모양으로 되어 있음을 알수 있다 => 이런 형태를 콜백 지옥이라고 부름

 


Promise 에 대해서

- 자바스크립트에서 제공하는 비동기를 간편하게 처리할 수 있게 해주는 object
- 정해진 장시간의 기능을 수행하고 나서, 정상적으로 기능이 수행되어졌다면 결과값을 전달해주고 문제가 발생한다면 에러를 전달해줌
- 비동기적으로 실행하는 작업의 결과(성공 or 실패)를 나타내는 객체
- 비동기의 결과를 객체화 시킨다는 점이 가장 큰 장점이자 특징

 

Promise 생성자

- 생성자 함수와 동일하게 new 연산자를 사용해 Promise 객체를 만들 수 있다
- 인자로는 Executor 가 들어가는데 resolve 와 reject 라는 두개의 함수를 매개변수로 받는 실행함
resovle
: 비동기 작업을 시작하고 모든 작업을 끝낸 후, 해당 작업이 성공적으로 이행이 된다면 resovle 함수를 호출

reject
: 비동기 작업 중에 오류가 발생하면 reject 함수를 호출

 

const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        const randomNum = Math.random();

        if( randomNum >= 0.2 ){
            resolve("10 이상입니다.")
        }else{
            reject("10 이하입니다.")
        }
    }, 2000)
})

 

Promise state (상태 )

대기 중인 promise 는 값과 함께 이행할 수도, 어떤 이유(오류)로 인해 거부될 수도 있음
그래서 다음과 같은 상태 중 하나를 갖음
  • 대기 (pending) : 비동기 처리 로직이 아직 완료되지 않은 상태
  • 이행 (fulfilled) : 비동기 처리가 완료되어 프로미스가 결과 값을 반환해준 상태
  • 거부 (rejected) : 비동기  처리가 실패하거나 오류가 발생한 상태

promise 의 state 과정

 

then ()

- 작업이 성공적으로 이행이 되었거나, 실패했을 때 어떠한 작업을 해야 하는데 이 작업은 then 메소드에 의해 실행된다
- then 메소드는 promise 객체에 붙여서 사용함
- promise.then(successCallback, failureCallback) 이러한 방식으로 콜백을 실행할 수 있다.
const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        const randomNum = Math.random();
        
        if( randomNum >= 0.2 ){
            resolve("10 이상입니다.")
        }else{
            reject("10 이하입니다.")
        }
    }, 2000)
})

promise.then(console.log)
// or
promise.then((value) => console.log(value))
  • 비동기 작업이 성공하면, 값을 반환해주는데 그 값을 생략해서 작성해도 된다.
promise
    .then((value) => console.log(value), (value) => console.log(value , "에러입니다"))
  • console.log(value) : promise 가 성공 (fulfiled) 했을 때를 위한 콜백 함수
  • console.log(value , "에러입니다") : 실패 (rejected) 했을 때를 위한 콜백 함수

 

Catch ()

error 가 발생했을 때 어떻게 처리할건지에 대한 메소드
const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        const randomNum = Math.random();

        if( randomNum >= 0.5 ){
            resolve("10 이상입니다.")
        }else{
            reject(new Error("10 이하입니다."))
        }
    }, 2000)
})

promise
    .then((value) => console.log(value)).catch(console.log)

catch 를 사용한 모습
catch 를 사용하지 않은 모습

  • catch 를 사용하면 에러를 핸들링할 수 있는 모습이다.

 

finally ()

성공하든, 실패하든 상관없이 어떤 기능을 마지막에 수행하고 싶을 때 사용하는 메소드
const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        const randomNum = Math.random();

        if( randomNum >= 0.5 ){
            resolve("10 이상입니다.")
        }else{
            reject(new Error("10 이하입니다."))
        }
    }, 2000)
})

promise
.then((value) => console.log(value))
.catch(console.log)
.finally(() => console.log("finally"))

 

Promise Chaining

then 메소드는 promise 객체를 리턴하고 인수로 받은 콜백 함수들의 리턴 값을 이어 받아서 체이닝이 가능하다.
const fetchNumber = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(1)
    }, 2000)
})


fetchNumber
.then(num => num * 2)
.then(num => num * 3)
.then(num => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(num - 1)
        }, 2000)
    })
}).then(console.log)
  • then 메소드는 promise 객체 또는 값을 반환해준다.
1. 체이닝하면서 에러핸들링하기
const getHen = () =>
    new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve("닭")
        }, 1000)
    })


const getEgg = (hen) => 
    new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(`${hen} => 달걀`)
        }, 1000)
    })


const cook = (egg) => 
    new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(`${egg} => 후라이`)
        }, 1000)
    })

getHen()
.then(hen => getEgg(hen))
.then(egg => cook(egg))
.then(meal => console.log(meal))
= 닭 => 달걀 => 후라이

// or

getHen()
.then(getEgg)
.then(cook)
.then(console.log)

이 상황에서 달걀을 받아오지 못하는 상황이라면 빵을 주도록 한다고 에러핸들링해보자.

const getHen = () =>
    new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve("닭")
        }, 1000)
    })


const getEgg = (hen) => 
    new Promise((resolve, reject) => {
        setTimeout(() => {
            reject(`${hen} => 달걀`)
        }, 1000)
    })


const cook = (egg) => 
    new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(`${egg} => 후라이`)
        }, 1000)
    })

getHen()
.then(getEgg)
.catch(error => {
    return "빵"
})
.then(cook)
.then(console.log)
= 빵 => 후라이

이런식으로 중간에 에러가 생긴다면, 다른 값을 반환해서 그 반환 값으로 진행해라. 라는식으로 핸들링할 수 있다.