비동기 방식을 알아보고, 콜백 지옥을 해결하기 위한 방법인 Promise와 Async/await을 알아본다.
동기(Synchronous) 방식은 하나가 끝날 때까지 다음작업을 수행할 수 없는 방식으로, 데이터의 요청과 결과가 한 자리에서 동시에 일어나는 것이다.
비동기(Asynchronous) 방식은 동시에 일어나지 않는 것으로, 요청 후 응답을 기다리지 않고 다른 활동을 한다.
함수가 끝난 뒤 실행되는 함수로, 함수를 만들 때 parameter로 함수를 받아서 사용한다.
콜백함수를 사용해서 코드를 작성해보았다.
console.log("start");
// cb: callback function
function login(id, pw, cb) {
setTimeout(() => {
console.log( '정보없음' );
cb(id);
}, 0);
}
const user = login("kim", "1234", user => {
console.log(user + "님 반갑습니다.");
});
console.log("finish");
위 코드를 실행하면 아래와 같은 결과가 나올 것 같지만 그렇지 않다.
start
정보없음
kim님 반갑습니다.
finish
실제 결과는 이렇게 나온다.
start
finish
정보없음
kim님 반갑습니다.
setTimeout()
는 비동기적으로 호출되는 함수이기 때문이다. 따라서 실행되는 과정은 다음과 같다
console.log("start");
를 실행한다.login()
을 실행하는데, 비동기적으로 실행되는 setTimeout()를 만나, 바로 다음 코드를 처리한다.console.log("finish")
를 실행한 후에야 비동기적 코드가 값을 반환하게 된다.콜백지옥(callback hell)이란 비동기 처리에 함수의 매개변수로 넘겨지는 콜백 함수가 반복되어 코드의 들여쓰기가 깊어지는 현상으로, 가독성이 떨어지고 코드 수정하기가 어려워진다.
다음 예제를 보자
function call( name, cb ){
setTimeout( function(){
console.log( name );
cb( name );
}, 1000 );
}
function back( cb ){
setTimeout( function() {
console.log( "back" );
cb( "back" );
}, 1000 );
}
function hell( cb ){
setTimeout( function() {
cb( "callback hell" );
}, 1000 );
}
call( 'Lee', function( name ){
console.log( name + "반가워" );
back( function( txt ) {
console.log( txt + "을 실행했구나" );
hell( function( message ){
console.log( "여기는 " + message );
} );
});
});
실행 결과
Lee
Lee반가워
back
back을 실행했구나
여기는 callback hell
콜백 함수가 3번 반복되면서 코드의 들여쓰기가 계속해서 깊어지는 것을 확인할 수 있다. 이와 같은 복잡도 증가와 예외처리의 어려움을 해결하기 위해 만들어진 것이 바로 프로미스(Promise) 다.
Promise는 비동기 처리 작업에서 성공과 실패를 분리해 메소드를 수행한다. 즉 Promise는 상태를 갖는다.
resolve(value)
: 작업이 성공적으로 끝난 경우then
으로 들어간다.reject(error)
: 작업이 성공적이지 않은 경우(에러 발생 시)catch
로 들어간다.const func1 = new Promise( ( resolve, reject ) => {
flag = true;
if (flag) resolve("성공");
else reject("실패");
})
//func1 실행값이 then에 들어온다.
func1.then( value => {
return value + "1";
}).then( result => {
//첫번째 then에서 리턴된 값이 여기 온다.
console.log(result);
}).catch( err => {
console.log( err );
});
실행 결과
성공1
promise의 상태
Pending
: Promise를 수행 중인 상태Fulfilled
: Promise가 reslove 된 상태Rejected
: Promise가 지켜지지 못한 상태, Reject 된 상태Settled
: fulfilled 혹은 rejected로 결론이 난 상태위에서 보았던 callback Hell 예제를 Promise를 사용해서 아래처럼 바꿀 수 있다.
function call(name) {
return new Promise( function (resolve, reject) {
setTimeout( function() {
console.log(name);
resolve(name);
}, 1000);
})
}
function back() {
return new Promise( function (resolve, reject) {
setTimeout( function() {
console.log("back");
resolve("back");
}, 1000);
})
}
function hell() {
return new Promise( function (resolve, reject) {
setTimeout( function() {
resolve("promise");
}, 1000);
})
}
call('Lee')
.then( function(name) {
console.log( name + "반가워");
return back();
})
.then( function(txt) {
console.log( txt + "을 실행했구나");
return hell();
})
.then( function( message ) {
console.log( "여기는 " + message);
});
실행 결과
Lee
Lee반가워
back
back을 실행했구나
여기는 promise
다른 예제를 한 번 더 보자
function login(id, pw, cb) {
setTimeout(() => {
console.log("사용자 입장");
cb(id);
}, 3000);
}
function getVideo(id, cb) {
setTimeout(() => {
cb( ['아이언맨1', '아이언맨2']);
}, 2000);
}
function getDetail( video, cb ) {
setTimeout(() => {
cb("비디오 제목은 : " + video);
}, 1000);
}
login('lee', '1234', user => {
console.log("user님 환영");
getVideo( user, (videos) => {
console.log( videos );
getDetail(videos[0], title => {
console.log(title);
});
});
});
실행 결과
사용자 입장
user님 환영
[ '아이언맨1', '아이언맨2' ]
비디오 제목은 : 아이언맨1
Callback 방식으로 작성하니 역시 들여쓰기가 깊어진다.
위 코드를 Promise 방식으로 바꿔보자
function login(id, pw) {
return new Promise( function (resolve, reject ) {
setTimeout( function() {
console.log("사용자 입장");
resolve(id);
}, 3000);
})
}
function getVideo(id) {
return new Promise( function (resolve, reject ) {
setTimeout( function() {
resolve( ['아이언맨1', '아이언맨2']);
}, 2000);
})
}
function getDetail(video) {
return new Promise( function (resolve, reject ) {
setTimeout( function() {
resolve("비디오 제목은 : " + video);
}, 1000);
})
}
login( 'Lee', '1234' )
.then( function(user) {
console.log('user님 환영');
return getVideo(user);
})
.then( function(videos) {
console.log( videos );
return getDetail( videos[0] );
})
.then( function(title) {
console.log( title );
});
실행 결과
사용자 입장
user님 환영
[ '아이언맨1', '아이언맨2' ]
비디오 제목은 : 아이언맨1
프로미스 기반 코드를 좀 더 쓰기 쉽고 읽기 쉽게 하기 위해 나온 것으로, Javascript의 비동기 처리 패턴 중 가장 최근에 나온 문법이다.(ES2017)
async
await
위 프로미스 예제를 async/await로 변환해보자
function login(id, pw) {
return new Promise( function (resolve, reject ) {
setTimeout( function() {
console.log("사용자 입장");
resolve(id);
}, 3000);
})
}
function getVideo(id) {
return new Promise( function (resolve, reject ) {
setTimeout( function() {
resolve( ['아이언맨1', '아이언맨2']);
}, 2000);
})
}
function getDetail(video) {
return new Promise( function (resolve, reject ) {
setTimeout( function() {
resolve("비디오 제목은 : " + video);
}, 1000);
})
}
async function exec() {
let user = await login('Lee', '1234');
console.log('user님 환영');
let videos = await getVideo(user);
console.log( videos );
let title = await getDetail(videos[0]);
console.log(title);
}
exec();
실행결과
사용자 입장
user님 환영
[ '아이언맨1', '아이언맨2' ]
비디오 제목은 : 아이언맨1