콜백함수, Promise, Async와 Await

Joo Yeong Park·2020년 11월 10일
1

develop

목록 보기
6/8

무슨일이지
요근래 카카오 로그인으로 골머리를 앓고있는데, 다들 간단히 잘하는 것 같은데 왜이렇게 어려운지!

postman으로 테스트 했을 때는 서버가 정상적으로 돌아간다고 생각했는데..😭😭😱 위와 같은 에러를 만났다. axios로 카카오로그인기능의 api를 호출했는데 한참이 지나서야 저런 에러가 뜬다.

ERR_EMPTY_RESPONSE인 것으로 보아 아무런 응답이 오지 않는 모양인데, 비동기 문제일 것이라고 생각한다. 그래서 이왕 비동기 문제를 만났으니 비동기 처리에 대해 다시 정확하게 공부하고 코딩해보려고 한다!(😇)

비동기 프로그래밍

👻비동기 프로그래밍이란?

프로그램의 메인 실행흐름을 멈춰서 기다리는 부분 없이 즉시 다음 작업을 수행할 수 있도록 만드는 프로그래밍 방식

자바스크립트에서는 코드가 순차적으로 실행이 되는데, 코드의 실행 결과를 기다려주지 않는 것이다! 아래 그림처럼👇

2번 노랑이 시킨 일을 3번 파랑이 처리하고 완료하는 동안 노랑이는 기다려주지 않고 이미 5번으로 가 버린 것이다. 파랑이가 완료한 결과를 5번 노랑이에게 준다고 할지라도 5번 노랑이는 결과물을 받을 준비가 되어있지 않는 것이다.

예를 들어 마치 내 상황 처럼 노랑 = 프론트, 파랑 = API 서버인 경우 프론트에서 서버를 호출해놓고 이미 다른 일을 처리하느라고 지나간 것이다. 그래서 동기 방식으로 생각하고 코딩한 내 계획대로 동작해주지 않는다.. 호출완료를 기다려 주지 않는 것! 이것이 바로 비동기 처리이다.

비동기 방식은 동기 방식(완료될 때까지 기다려주는 것)보다 복잡하지만 2번 노랑이처럼 코드의 실행 결과 처리 및 활용을 별도의 채널에 맡겨두고 그 시간 동안 다른 작업을 할 수 있으므로 자원을 효율적으로 사용할 수 있다.

이제 파랑이가 완료한 작업물을 받으러 가보자

콜백 함수

👻콜백함수란?

프로그래밍에서 다른 코드의 매개변수로서 넘겨주는 실행 가능한 함수로 어떤 이벤트 발생 후 매개변수로 전달한 함수가 다시 호출되는 것을 의미

예를 들면 이렇게 post함수의 매개변수에 익명함수를 넣어준 것이다.

router.post("/signup", (req, res, next)=>{
    var body = req.body

    models.user.create({
      user_id: body.id,
      user_pw: body.pw
    })
    .then( result => {
      res.json(util.successTrue())
    })
    .catch( err => {
      res.json(util.successFalse(err))
    })
});

콜백함수를 사용하는 이유는 비동기 데이터처리를 하기 위해서이다. 하지만 한번의 콜백으로 결과가 나오는 평화로운 상황만 있는게 아니라 콜백함수가 계속 중첩되는 상황이 발생하기도 한다.

콜백을 남용한 결과 함수의 깊이가 깊어질수록 난해한 코드들을 '콜백지옥'이라고들 한다!
콜백hell
더 깊이 있는 설명은 요기여기를 참고하면 좋겠다.
그럼 이젠 콜백지옥을 해결하는 방법 Promise를 알아보자.

Promise

콜백지옥에서 벗어날 방법은 Promise라고 한다.
👻Promise란?

Promise가 생성될 때 꼭 알 수 있지는 않은 값을 위한 대리자로, 비동기 연산이 종료된 이후의 결과값이나 실패 이유를 처리하기 위한 처리기를 연결할 수 있도록 하는 객체

Promise를 사용하면 비동기 메서드에서 마치 동기 메서드처럼 값을 반환할 수 있다. (다만 최종 결과를 반환하는 것이 아니라 Promise 객체를 반환해서 미래의 어떤 시점에 결과를 제공한다.)

즉, 언어에서 지원하는 비동기처리에 사용되는 객체라고 보면 되겠다.

Promise의 상태

Promise는 다음 세 가지 중 하나의 상태를 가진다.

  • Pending(대기) : 이행/거부되지 않은 초기 상태
  • Fulfilled(이행) : 연산 성공적 완료 상태
  • Rejected(거부) : 연산 실패 상태
function testPromise() {
	return new Promise((reslove, reject) => {	// pendeing 상태
		setTimeout(function() {
      		if(bool){
                resolve("fulfilled 상태");
            }
            else{
                reject("rejected 상태");
            }
    	}, 1000);
	});
}

testPromise(true)
.then(result => {
  	console.log(result);
})
.catch(error => {
  	console.log(error);
})

Promise 생성자에서 콜백함수로 실행 함수를 지정하는데 함수형 파라미터인(reslove, reject)를 인자로 받는다. 실행함수는 프로미스를 이행할 때는 resolve를, 거부할 때는 reject를 호출한다.

실행함수로 프로미스가 이행되면 .then() 호출로 할 일을 정의하고 거부됐을 때는 .catch() 호출로 할 일을 정의한다.

.then().catch()메서드는 새로운 Promise 객체를 반환하므로 비동기 동작을 연결시킬 수 있다.

Promise의 속성, 메서드, 예제는 여기에서 확인 가능!

Promise 와 Ajax

Ajax는 Asynchronous Javascript And Xml의 약자로 Javascript를 사용한 비동기 통신이며 클라이언트와 서버간 xml데이터를 주고받는 기술이다.

웹 프로그래밍을 하다보면 REST API호출을 위해 ajax를 자주 접하게 된다.

fetch든 axios든 메서드의 인자로 API의 URL을 받고 API호출 결과로 미래의 어떤 시점에서 받게 될 Promise객체를 return 받는다. 네트워크 지연때문에 바로 결과값을 얻지 못하는 상황에서 미래의 어떤 시점에서 받을 수 있는 Promise의 특성을 이용한 것이다.

axios.post(`${url}/user/kakao`, headers, body)
.then(result => {
	consol.log(result)
})
.catch(error => {
  	consol.log(error)
})

그래서 이런식으로 return 받은 Promise의 결과를 가지고 .then()이나 .catch()인자로 콜백 함수를 넣어 동작하는 코드를 만들 수 있다.

잘못된 Promise사용의 예

foo.someFunction()
.then(function(result){
    // ... 어떤 로직이 수행 된다.
    goo.anotherFunction()
    .then(function(result2){
        // ... 어떤 로직이 수행된다.
    })
    .catch( function(err){
        console.log(err);
    })
})

프로미스를 사용해 다시 콜백을 만드는 코드이다.
위의 코드를 수정하면

foo.someFunction()
.then(function(result){
    // ... 어떤 로직이 수행 된다.
    return goo.anotherFunction()
})
.then(function(result2){
    // ... 어떤 로직이 수행된다.
})
.catch( function(err){
    console.log(err);
})

.then().catch()는 다시 Promise를 return하기 때문에 이 Promise객체의 인자로 넘긴 콜백함수 리턴값(Promise)를 다시 .then().catch()으로 접근할 수 있다. 계속해서 연결해서 호출하는 방식을 Promise의 메서드 체이닝이라고 한다.

메서드 체이닝이 너무 반복되면 다시 콜백지옥에 빠질지도 모른다.

참고: victolee님 블로그

Async & await

Promise를 보다 간결하게 작성하게 하는 키워드가 바로 async와 await이다.
async와 await는 Promise를 기반으로 하기 때문에 Promise에 대해 먼저 알아두는 것을 추천한다.

콜백의 깊이가 깊지 않을 때는 간편하게 콜백함수나 프로미스를 사용하는 것이 나을 수 있다.

async 함수

async 키워드는 function앞에 위치한다.

async function hi() {
	return 1; 
}

async가 앞에 붙은 function은 항상 Promise를 반환한다. (위의 코드처럼 Promise가 아닌 값을 반환하더라도 Promise로 값을 감싸 resloved promise로 반환한다.)

위의 코드는 result가 1인 resloved promise가 반환되는 것이다.

await

await는 async함수 안에서만 동작하는 키워드이다.

async 키워드를 사용한 함수 안에서 Promise를 반환하는 비동기 함수 앞에 await 키워드를 사용한다.

자바스크립트는 await키워드를 만나면 비동기함수가 리턴하는 Promise가 처리될때까지 기다리고 그 이후에 반환된 Promise로부터 결과값을 추출해준다.

즉 await를 사용하면 동기 처리와 동일한 흐름으로 코드를 작성할 수 있게 된다.

async function hi() {
	const data = await axios.post(`${url}`, headers, body)
	console.log(data)  	
}

비동기 함수의 Promise사용 여부

async와 await을 사용한다 하더라도 호출한 비동기 함수가 Promise를 사용하지 않는다면 async/await이 적용되지 않는다.

비동기 함수가 Promise 사용 X

async function test(){
    await foo(1, 2000)
    await foo(2, 500)
    await foo(3, 1000)
}

function foo(num, sec){
    setTimeout( function(){
        console.log(num);
    }, sec);
}

test();

위의 경우 Promise를 사용하지 않은 비동기 함수 foo()를 호출할 때 async/await을 사용하여 동기방식처럼 1, 2, 3순서로 응답되기를 기대한 코드이다.

실행해 보면 결과는 다음과 같다.

결과는 비동기 코드로 수행되었음을 알 수 있다.

비동기 함수가 Promise 사용 O

async function test(){
    await foo(1, 2000)
    await foo(2, 500)
    await foo(3, 1000)
}

function tomaotP(num, sec){
    return new Promise(function(resolve, reject){
        setTimeout( function(){
            console.log(num);
            resolve("async는 Promise방식을 사용합니다.");
        }, sec);
    });
}
test();

비동기 함수에서 Promise를 반환하도록 수정한 코드이다. 위의 코드를 실행하면

의도한대로 동기적으로 동작한다.

이유는 위에 설명한 것 처럼 await는 Promise를 반환하는 함수를 받기 때문이다. 비동기 함수가 반환하는 Promise를 await에서 받아 동기적으로 수행하게 해주기 때문!

비동기함수 예제들의 출처는 여기! 👈더 다양한 async/await 사용 예제들이 더 있다.

정리

비동기 처리를 할 수 있는 방법으로 세 가지를 알아봤다.

  • 콜백함수 사용
  • Promise 사용
  • async/await 사용

간단한 구문의 경우 콜백함수와 Promise를 적절히 사용하는 것이 유리하고,
콜백함수의 깊이가 깊거나 복잡해지는 경우 가독성이 더 좋은 async/await을 사용하면 좋겠다.

각각의 장단점이 있어 적절히 사용할 줄 아는 능력을 키워보는 것도 좋을 것 같다!

길고 길었던 비동기 처리 포스팅을 마치며,
그동안 어렴풋이만 알고있던 지식들을 정리하고 직접 코딩하면서 또 한번 느낀 것은 어렴풋이 흉내만 내는 것이 모르는 것보다 못하다..!

정확하게 알고 쓰자!

이만 흉내만 낸 코드를 고치러 총총

profile
웹 개발자를 꿈꾸는 삐약

0개의 댓글