본문 바로가기
Programming/WEB

[JS] 자바스크립트의 비동기 처리 패턴 - 콜백(callback), 프로미스(promise)

by 코딩의성지 2021. 3. 17.

자바스크립트에서 비동기 처리를 하는 것은 특정 코드의 로직이 끝날때까지 코드의 실행을 멈추지 않고 다음 코드를 바로 실행하는 것을 의미한다. 

 

보통 응답과 요청 구조로 되어있는 통신에서 비동기처리를 많이하는데, 자바스크립트에서는 이러한 비동기 처리를 위해 아래 네가지 패턴을 사용했다.

 

1. callback

먼저 비동기 처리의 대표적인 예시는 여러분들도 다들 아시는 ajax를 이용한 코드이다.

ajax 통신은 예~~전에 웹프로젝트를 할때 이미지나 데이터를 서버에서 가져와서 화면에 뿌려줄때 사용했던 통신 방식이다.

아래 예제를 먼저 보자.

function getUserInfo() {
	var userInfo;
	$.get('https://localhost:8443/users/testId1', function(response) {
		userInfo = response;
	});
    return userInfo;
}
console.log(getUserInfo()); //undefined

위의 예시는 id 값을 파라미터로 넣어서 서버에 던지면 전체 user 정보를주는 간단한 비동기 함수인데,

아래 log 결과를 보면 실제 user 정보가 채워지지 않고 undefined가 뜨고 있다. 

이게 바로 비동기 처리가 되서 그러는데 이렇게 내부의 $.get 이 처리되는 걸 기다려 주지 않고 바로 다음 내용인 return을 실행해서 그렇다.. 

 

여기서 여러분은 이런 의문을 가질 것이다. 왜 이런 로직을 쓰지??

이런 비동기 처리가 필요한 이유는 .. ! 웹 서비스는 수많은 요청이 들어오는데 .. 동기로 순차적으로 데이터를 처리해버리면 그많은 서비스가 빠르게 처리가 될수가 없기에 이런 비동기 처리를 쓴다.

 

그런데 앞서 말했듯 ... 그냥쓰는건 문제가 많ㅇ아보인다. 우리는 이를 Callback 함수를 이용해서 해결할 수 있다. 위의 코드를 callback 함수로 개선해보면 아래와 같이 개선이 가능하다.

function getUserInfo(callbackFunc) {
	$.get('https://localhost:8443/users/testId1', function(response) {
		callbackFunc(response);
	});
}

getUserInfo(function(userInfo) {
	console.log(userInfo); //response 의 값이 userInfo에 전달됨
});

callback 함수는 데이터가 준비가 되야 호출 되기 때문에, 우리가 원하는 로직이 정상적으로 수행되고 난 뒤 호출이 된다.

즉 비동기 처리의 문제를 해결할 수 있다.

 

그런데.. 이렇게 처리하다보면 또 문제가 하나생긴다.

바로 콜백 지옥이라는 놈인데, 옛날(?)부터 웹개발을 해오신분들은 그런경험 해보신적있을꺼다.

예를 들면, 어떤 데이터를 화면을 뿌려주기 위해 기존에 존재하는 인증도 해주고, 데이터 가공도 해주고 하면서.. 수많은 callback을 계속 호출해주면서 오른쪽으로 삼각형 형태( ▷) 로 아주 큰 호출에 호출에 호출을 하는 콜백지옥을 맛보신적 있을것이다. 이런 코드는 이해하기도 어려울 뿐더러 유지보수도 힘들다... 최대한 지양해야한다.

 

그래서 이런방법을 해결하기위해 나온게 바로 promise 패턴이다.

 

2. promise

promise는 콜백의 비효율성을 개선한 자바스크립트 비동기 처리 패턴이다.

위에서 콜백을 사용해던 방식을 promise를 사용해서 바꿔보자..

let getUserInfo = new Promise((resolve, reject) => {
    	$.get('https://localhost:8443/users/testId1', function(response) {
        	resolve(response);
        });
});


// getUserInfo() 함수가 실행되고 나서 실행되는 then 함수
getUserInfo.then((userInfo) => {
	console.log(userInfo); //response 의 값이 userInfo에 전달됨
});

 

위처럼 프로미스 API 를 이용해서 구현이 가능하다. 이제 간단하게 구조를 설명드리겠다.

 

프로미스는 Pending, Fulfilled, Rejected 이렇게 3가지 상태가 있다.

 

Pending은 말 그대로 대기 상태이다. 즉 비동기 처리가 아직 완료되지 않은 상태를 의미한다.

Fulfilled는 비동기 처리가 완료되고 프로미스가 그 결과값을 반환한 상태를 의미한다.

Rejected는 실패가 됐을때 상태를 의미한다.

 

위의 코드에서

new Promise() 가 호출 될때 Pending 상태가 된다.

 

그리고 여기서 콜백함수 의 resolve를 실행하면 Fulfilled 상태가 된다.

여기서 Fulfilled 상태가 되어야.. 아래의 then() 로직이 돌아간다.

 

우리가 위에서는 사용하진 않았지만 아래처럼 reject 도 추가할수 있다.

let getUserInfo = new Promise((resolve, reject) => {
    	$.get('https://localhost:8443/users/testId1', function(response) {
        	if(response) { resolve(response);}
        	reject(new Error("error!!"));
    	});
});

// getUserInfo() 함수가 실행되고 나서 실행되는 then 함수
getUserInfo.then((userInfo) => {
	console.log(userInfo); //response 의 값이 userInfo에 전달됨
}).catch(function(err) {
	console.error(err); // error 발생시 error!! 출력 
});

 

reject는 비동기 함수인 get함수의 로직이 처리되지 않을때 , 실패상태( Rejected) 상태가 된다.

그리고 catch에서는 실패한 이유를 받아낼수 있다.

 

이제 이렇게 프로미스 패턴을 쓰게 되면 콜백 지옥을 피할수 있다.

왜냐면 아래처럼 Promise Chaining 이 가능하다. 아래 예제는 user정보를 가져올때 먼저 유효한 회원인지 인증을하고, 데이터를 파싱하고, 그걸 화면에 부려주는 로직을 구현했다고 가정해본다.

 

var userInfo= {
	id: 'testId1',
    pw: '1234'
};

function authUser() {
	return new Promise({
    	// 인증 로직
    });
}

function processData() {
	return new Promise({
    	// 데이터 처리
    });
}

function viewUser() {
	return new Promise({
    	// 화면에 정보 뿌리는 로직
    });
}

getUserInfo(userInfo)
	.then(authUser)
    .then(processData)
    .then(viewUser);

이렇게 여러개의 then을 연결해서 사용이 가능해진다. 이렇게 하면 콜백지옥을 빠져나올수 있다.

 

자... 모두들 promise 패턴 이해 되셨나?

 

다들 열공하자 ..

 

 

 

반응형

댓글