Javascript Promise

2022. 12. 21. 16:18JavaScript

목차
1. Promise의 탄생 : 콜백 지옥(callback hell)
2. Promise
  - 기본 문법(Syntax)
  - 예제(Example)

 

1. Promise의 탄생 : 콜백 지옥(callback hell)

 

비동기 작업을 위해 사용되는 콜백의 특성상 비동기 이후에 처리될 작업들을 콜백 내부에 작성해주어야 한다.

콜백 내부에 콜백을 쓰다보면 오른쪽 아래로 코드가 내려가 모양이 괴랄하게 변하게 된다.

이를 콜백 지옥(callback hell)이라고 한다.

 

아래의 예제코드로 콜백 지옥을 이해해보자.

 

const avante = (callback) => {
    setTimeout(() => {
        console.log('avante go')
        callback()
    }, 3000)
}

const sonata = (callback) => {
    setTimeout(() => {
        console.log('sonata go')
        callback()
    }, 2000)
}

const genesis = (callback) => {
    setTimeout(() => {
        console.log('genesis go')
        callback()
    }, 1000)
}

 

콘솔창에 자바스크립트를 실행한뒤 3초 뒤에 "avante go", 2초 뒤에 "sonata go", 1초 뒤에 "genesis go"를 찍고 싶다면, 어떻게 코드를 찍어야 할지 생각해보자.

 

아래의 코드를 실행하여 답을 확인해보자.

 

avante(() => {
    sonata(() => {
        genesis(() => {
            console.log('End')
        })
    })
})

// "avante go"
// "sonata go"
// "genesis go"
// "End"

 

코드를 실행하면, 원하는 대로 찍혀 6초 뒤에 마지막 console.log("End")가 찍힌다. 벌써부터 모양이 마음에 들지않는다.

 

이번에는 좀 더 복잡하게, 차례대로

 

"avante go"

"avante go"

"genesis go"

"sonata go"

"avante go"

"sonata go"

 

그리고 마지막으로 "End"를 찍어보자.

 

avante(() => {
    avante(() => {
        genesis(() => {
            sonata(() => {
                avante(() => {
                    sonata(() => {
                        console.log('end')
                    })
                })
            })
        })
    })
})

// 'avante go'
// 'avante go'
// 'genesis go'
// 'sonata go'
// 'avante go'
// 'sonata go'
// 'End'

 

원하는 대로 콘솔창에 출력은 되었지만, 이대로 가다가는 화면 밖으로 코드가 나가버릴거 같다.

코드가 아래로 진행될 때 가독성을 위해 탭을 누르는데, 그 때, 생긴 여백을 탭 인덱스라고 한다.

 

개발자들은 이러한 콜백지옥 문제를 해결하기 위해서 Promise라는 문법을 고안해냈다.

 

 


2. Promise

 

<그림 1> 클라이언트 서버, 데이터 베이스의 통신

위의 상황에서 클라이언트와 는 서버에서 요청에 대한 응답을 언제 받을지 모르고 있고, 서버는 데이터 베이스에게 요청에 대한 응답을 언제 받을지 모르는 상황이다. 이 때, 비동기적으로 코드를 처리해야 클라이언트 측에서 좀 더 효율적으로 작업을 처리할 수 있게된다. 

 

통신의 측면에서 비동기적인 코드를 처리하기위해 발생한 콜백 지옥과 해결하기 위해서 나타나게된 문법이다.

 

기본 문법(Syntax)

 

function 선언과 화살표 함수로 나타낸 Promise의 문법이다.

 

new Promise(function (resolve, reject) {});
new Promise((resolve, reject) => {});

new Promise를 실행하게 되면 Promise라는 특별한 객체를 생성하여 반환한다.

Promise 안의 콜백 함수는 실행자 함수라고도 하며, 실행자 함수가 실행되면 Promise 객체 내부에 영향을 주게된다.

 

콜백 매개변수 resolvereject가 있는데,

각각 resolve는 성공했을 경우 실행할 함수, reject는 실패했을 경우 실행할 함수이다.

(예제에서는 다루지 않겠지만, if문이나 try_catch문과 같은 문법으로 예외처리나 분기처리를 해줘야한다)

 

Promise 객체 안에는 Promise에서 사용가능한 메서드들과 상태가 있다.

 

메서드 : then, catch, finally

성공(resolve) 했을 때 실행된다.

Promise.then((success) => {
    console.log("success")
})

실패(reject) 했을 때 실행된다.
Promise.catch((failed) => {
    console.log("failed")
}
)

끝나고 항상 실행된다.
Promise.finally(() => {
    console.log("always")
}
)

 

Promise.then(); Promise.catch(); Promise.finally() 모두 Promise 객체를 반환한다.

 

반환된 Promise 객체에는 메서드를 사용할 수 있어, Promise.then().then().then()...처럼 사용이 가능해진다.

 

이렇게 반환된 객체에 바로 메서드를 사용해서 값을 이어받도록 연결하는 것을 프로미스 체이닝이라고 한다.

 

 

아래의 상태(State)는 Promise 객체 안에 있는 특별한 프로퍼티로 State와 Result가 있는데, 이는 개발자가 접근할 수 없다. 그러나 위의 메서드들을 이용하여 접근이 가능해진다.

 

상태(State)

<pending>
> 비동기 코드가 완료되지 않은 상태. 즉, 요청 중 상태를 의미한다.

<fulfilled>
> resolve()가 호출되는 순간 <pending>에서 <fulfilled>로 상태가 변한다.

<rejected>
> reject()가 호출되는 순간 <pending>에서 <rejected>로 상태가 변한다.


생성된 Promise 객체는 상태에 따른 결과 값을 담고 있다.

결과(result)

{ state : <pending>, result : undefined }

{ state : <fulfilled>, result : "resolve 실행 값 = value"}

{ state : <rejected>, result : "reject 실행 값 = error"}

 

Promise 객체 안에 존재하는 특별한 프로퍼티로 개발자가 외부에서 접근이 불가능하다.

 

이해를 돕기 위해 그림과 아래의 코드를 준비했다.

 

<그림 2>

<그림 2>에서는 생략되었지만 왼쪽에서 실행자 함수가 실행되어 성공, 실패여부에 따른 분기를 나타낸것이다.

 

실행자 함수의 결과에 따라 Promise객체에 즉각적으로 영향을 주도록 자바스크립트에서 구현해놓은 것이다.

 

그러니 우리는 resolve와 reject에만 신경을 써서 작업을 해도 큰 문제는 없다.

 

아래의 코드는 콜스택과 백그라운드에 대해 이해하고, 위의 설명을 보고나면 쉽게 이해가 가능한 코드이니 가볍게 한번 보고 지나가도록 하자.

 

const foo = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve("success")
        console.log(foo) // Promise{ 'success' } - 2
        reject("failed")
        console.log(foo) // Promise{ 'success' } - 3
    }, 3000)
})

console.log(foo) // Promise{ <pending> } - 1

foo.then((data) => {
    console.log(data) // success - 4
});

foo.finally(() => {
    console.log("always") // always -5
})

/*
Promise { <pending> } - 1
Promise { 'success' } - 2
Promise { 'success' } - 3
success - 4
always - 5
*/

 


예제(Example)

 

위의 callback에서 풀었던 문제를 가져와 함수들(avante, sonata, genesis)을 Promise문법을 사용하여 바꿔보도록 하자.

 

const success = true

const avante = () => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if(success){
            	resolve('avante go')
            } else {
            	reject('failed')
            }
        }, 3000)
    })
}

const sonata = () => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if(success){
            	resolve('sonata go')
            } else {
            	reject('failed')
            }
        }, 2000)
    })
}


const genesis = () => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if(success){
            	resolve('genesis go')
            } else {
            	reject('failed')
            }
        }, 1000)
    })
}

 

각 함수를 실행하면 Promise 객체를 반환하는 함수를 만들었다.

 

이를 이용해 아까 풀었던 문제를 Promise의 메서드를 이용해 다시 한번 풀어보자.

 

"avante go"

"sonata go"

"genesis go"

 

위의 순서대로 콘솔창에 출력하라.

 

console.time('x')
avante()
    .then((data) => {
        console.log(data)
        return sonata()
    })
    .then((data) => {
        console.log(data)
        return genesis()
    })
    .then((data) => {
        console.log(data)
        console.timeEnd('x')
    })
    
/*
avante go
sonata go
genesis go
x: 6.022s
*/

 

코드를 실행하는 데에 총 6초가 걸렸고, 순서대로 출력했으므로 성공이다.

 

then은 resolve가 실행되면 그 값을 가져와 출력해주는 메서드이다. 출력된 값은 resolve에서 then으로 넘어와 출력된 값들이다.

 

Promise를 사용해 callback의 문제점인 탭 인덱스 문제가 해결되어 옆으로는 가지않지만, 아래로 길게 뻗어 나가게 된다.

 

callback의 문제는 해결했지만, 프로미스는 객체를 만드는 것이 어렵고, 사용할 때 코드량이 많아진다는 한계가 존재한다.

 

Promise의 한계를 극복하기위해 나온것이 async / await 문법이다.


 

'JavaScript' 카테고리의 다른 글

Javascript 통신(AJAX)  (0) 2023.01.21
Javascript async / await  (0) 2022.12.25
17. Javascript 심화학습  (0) 2022.11.27
16. Javascript 배열과 메서드  (0) 2022.11.27
14. Javascript.실습 C.R.U.D  (1) 2022.11.20