쌓고 쌓다

[JavaScript] Promise(프로미스) + 콜백 지옥 본문

프로그래밍/JavaScript

[JavaScript] Promise(프로미스) + 콜백 지옥

승민아 2023. 1. 13. 00:03

콜백 함수

매개변수로 전달하는 함수를 콜백 함수라고 한다.

콜백함수는 즉시 실행할 수도 있고, 나중에 실행할 수도 있다.

 

동기 콜백(Synchronous callback)

function printImmediately(print) {
    print()
}

printImmediately(() => console.log('즉시 콜백함수 실행'))

 

비동기 콜백(Asynchronous callback)

function printWithDelay(print, timeout) {
    setTimeout(print, timeout)
}

printWithDelay(() => console.log('async callback'), 2000)

 

콜백 지옥

 

아래의 콜백 함수 add가 있다고 하자.

function add(x, callbacK) {
    const sum = x + x
    console.log(sum)
    callback(sum)
}

인자로 받은 값 x를 x+x하여 출력하고 그 값을 callback에 넘겨 실행한다.

 

아래의 콜백 중첩을 보자...

add(2, res => {
    add(res, res => {
        add(res, res => {
            console.log('end')
        })
    })
})

처음 인자 x를 2로 받아 sum = 2+2하여 출력하고 그 값(sum)을 callback 함수에 전달한다

그런데 또 그 callback 함수는 받은 그 값 4를 4+4하여 출력하고 그 값(8)을 콜백 함수에 전달한다.

그 콜백함수는 받은 값을 8+8하여 출력하고 콜백함수에 또 넘긴다.

마지막의 콜백함수는 console.log('end')로 출력하고 끝난다.

 

이렇듯 하나의 작업을 콜백으로 결과를 받아 순차적으로 다음 작업을 진행하고자 할 때.

이벤트 처리나 서버 통신을 위한 비동기 처리로 콜백 패턴을 사용하여 처리 순서를 보장할 때.

이런 콜백의 중첩인 콜백 지옥이 나타난다.

 

 

Promise

프로미스는 비동기 처리에 사용되는 객체이다.

비동기 처리란? 특정 코드의 실행이 완료될 때까지 기다리지 않고 다음 코드를 수행하는 특성이다.

프로미스는 주로 서버에서 받아온 데이터를 화면에 표시할 때 사용함.

 

왜 Promise가 필요할까?

서버에 데이터를 요청하고 받아오기도 전에 데이터를 받아온 것 마냥 화면에 표시할 때 오류가 발생한다.

비동기 처리를 동기 처리하기 위해 프로미스가 필요한 것이다.

 

1. Producer

const promise = new Promise((resolve, reject) => {
    console.log('doing something...')
})

실행하면 'doing something...'이 출력된다. 이것으로 promise를 생성하자마자 executor가 자동실행이 되는 걸 알 수 있다.

이렇게 작성하면 네트워크 요청을 사용자가 요구하지도 않았는데 불필요한 통신이 이뤄진다.

 

 

resolve

const promise = new Promise((resolve, reject) => {
    console.log('doing something...')
    setTimeout(() => {
        resolve('성공')
    }, 2000)
})

기능이 정상적으로 잘 수행했다면 resolve라는 콜백 함수를 호출하여 전달하면 된다.

 

 

reject

const promise = new Promise((resolve, reject) => {
    console.log('doing something...')
    setTimeout(() => {
        reject(new Error('실패'))
    }, 2000)
})

 

2. Consumers

then, catch, finally를 이용하여 producer에서의 값을 얻어 사용할 수 있다.

 

 

then

promise.then((value) => {
    console.log(value)
})

value라는 파라미터는 promise가 정상적으로 수행되어 resolve를 통해 전달된 값이 들어온다.

resolve 되었다면 resolve('성공')으로 위의 예제에서는 '성공'이 들어온다.

만약 promise가 reject되어 에러가 발생했다고 가정해 보자.

const promise = new Promise((resolve, reject) => {
    console.log('doing something...')
    setTimeout(() => {
        reject(new Error('실패'))
    }, 2000)
})
promise.then((value) => {
    console.log(value)
})

잡히지 않은 에러가 발생했다.

then으로 성공적인 경우만 잡았기에 Uncaught가 발생한 것이다.

이때 실패 케이스를 위해 catch를 사용한다.

 

catch

const promise = new Promise((resolve, reject) => {
    console.log('doing something...')
    setTimeout(() => {
        reject(new Error('실패'))
    }, 2000)
})
promise
    .then((value) => {
        console.log(value)
    })
    .catch((error) => {
        console.log(error)
    })

catch로 실패 케이스를 잡아준다.

catch

promise의 then을 호출하면 똑같은 promise를 리턴하기에 catch를 호출할 수 있는 것이다.

이것을 체이닝이라한다.

 

finally

promise
    .then((value) => {
        console.log(value)
    })
    .catch((error) => {
        console.log(error)
    })
    .finally(() => {
        console.log('finally')
    })

성공 실패에 관계없이 무조건 호출된다.

finally

 

promise chaining

then은 promise를 전달할 수도 있지만, 값을 바로 전달할 수도 있다.

1초 1초가 소요되어 총 2초가 소요된다. =>> then then 비동기적인 것들을 묶어 처리가 가능하다.


Error Handling

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))

 

 

 

 

hem에서 egg를 받아올때 reject가 되었다고 해보자.

 

젤 끝에 catch를 작성하여 에러 처리가 가능하다.

getEgg에서 에러가 발생헀지만 아래로 쭉 아래로 전달되어 catch에서 잡혀진다.

 

 

만약 getEgg 다음에 에러가 났다면 그것을 빵구를 메꿔 처리 또한 가능하다.

즉, catch의 순서에 맞춰 에러 처리 또한 가능하다는 것이다.

egg 대신 '마트에서 계란사기'로 대체했다.

대체 결과

 

 

 

 

 

 

+ 콜백함수를 전달할 때 받아온 값을 다른 함수로 바로 하나를 전달할 때

 

암묵적으로 바로 전달이 가능하여 아래처럼 생략이 가능하다.

생략

 

레퍼런스

https://youtu.be/JB_yU6Oe2eE

https://ko.javascript.info/promise-basics

Comments