이 글은 개인적인 공부의 목적으로 작성하는 글이므로 오류가 있을 수 있으니 참고해주시기 바랍니다!
지난 [JavaScript] 비동기처리와 콜백함수 포스팅에서 비동기작업의 순차적 처리를 하는 기본적인 방식으로 콜백함수를 사용할 수 있다고 했다.
복잡하지 않은 코드를 작성할 때는 콜백함수를 이용해서 비동기 처리를 해도 문제가 없지만, 콜백함수를 계속적으로 중첩된 사용을 해야하는 상황이라면 콜백지옥(비동기 함수의 처리 결과에 대한 처리를 다시금 해당 비동기함수의 콜백함수 내에서 처리해야하므로 연쇄적인 콜백함수 사용시 콜백지옥이 발생!)을 야기할 수 있기에 이를 보완하기 위해 ES6에서 Promise 객체가 새롭게 추가되었다고 한다.
아래 코드는 앞선 [JavaScript] 비동기처리와 콜백함수 글에서 콜백함수를 이용하여 비동기작업을 처리했던 코드블럭이 프로미스 객체를 이용하여 다시 구현된 것이다.
function findUser(id) {
let user;
return new Promise((resolve,reject) => {
setTimeout(function () {
user = {
id: id,
name: "User" + id,
email: id + "@test.com",
}
resolve(user)
}, 1000);
});
}
findUser(5).then(function (result){
console.log("user: ", result);
});
// user: {id: 5, name: "User5", email: "5@test.com"}
위 코드에서 선언된 findUser함수는 콜백함수를 사용한 비동기 작업 처리에서와는 달리 콜백함수를 매개변수로 넘기지 않고, Promise 객체를 생성하여 리턴하고 있다.
findUser함수가 호출되면 리턴된 Promise객체에 then 메서드를 호출하며 Promise객체에서의 결과값(resolve함수가 이행된 결과값)을 가지고 실행할 코드를 콜백함수의 형태로서 then메서드의 인자로 넘겨주게 된다.
콜백함수를 통한 비동기처리와의 차이점은, 함수를 호출하면 Promise타입의 결과값이 리턴되고 이 결과값으로 다음에 수행할 작업을 처리하는 것이다.
Promise는 new라는 키워드와 함께 생성자를 이용하여 생성된다. 이 생성자는 executor라는 실행함수를 인자로 받는데, 그 executor함수에는 또 resolve, reject라는 2개의 함수형 매개변수를 가진다.
그렇게 생성된 Promise 객체를 변수에 할당하여 사용하는 경우도 있는데, 위의 코드처럼 특정 함수의 리턴값으로 바로 사용되는 경우가 더 많다고 한다.
executor함수 내부에서 비동기 작업을 실행하는 코드가 작성되며, 해당 작업이 성공하였을 경우 성공한 결과 값을 리턴하는 resolve함수가 호출되고 실패할 경우 에러처리를 하는 reject함수가 호출된다.
위 코드의 경우 성공적으로 이행할 경우의 resolve함수만 호출되도록 작성되었는데, id 인자로 입력된 숫자 5가 user 변수에 잘 할당되어 성공적으로 이행된 user 객체가 then이 가지는 콜백함수의 인자(result)로서 잘 전달되어 출력되는 것을 볼 수 있다.
작업 실패 시 에러를 리턴하는 reject함수를 추가하면 코드는 아래와 같다.
function findUser(id) {
let user;
return new Promise((resolve,reject) => {
setTimeout(function () {
user = {
id: id,
name: "User" + id,
email: id + "@test.com",
}
if(user){
resolve(user)
}else {
reject(new Error('User is not defined'));
}
}, 1000);
})
}
findUser(5).then(function (result){
console.log(result);
}).catch(function (err) {
console.log(err);
})
executor 내 비동기 코드의 처리가 실패할 경우, reject함수에 인자로 전달한 Error객체가 findUser함수 호출부에서 (then 메서드를 호출하지 않고) 바로 catch 메소드의 인자로 콜백함수 형태로 전달되어 Error:'User is not defined'가 콘솔창에 출력된다.
catch 메서드를 사용하지 않고 then 메서드의 두 번째 인자로 에러처리를 넘겨줄 수도 있지만, 첫 번째 인자에서 발생하는 오류를 잡아내지 못한다는 등의 보완이 필요한 부분이 있어 가급적 catch 메서드를 사용하여 에러처리를 하는 것이 권장된다고 한다.
위의 코드에서 봐왔듯이, 프로미스의 처리 과정을 의미하는 3가지 상태(states)가 존재한다. 즉, new Promise()로 프로미스 객체를 생성하고 종료될 때까지 아래와 같이 3가지 상태를 갖는다.
앞에서 언급했듯이, 비동기 함수의 처리결과를 가지고 다른 비동기 함수를 호출하는 경우, 함수의 호출이 중첩되어 복잡도가 높아지는 콜백지옥이 발생한다.
Promise객체를 반환한 비동기 함수는 프로미스 후속처리 메소드인 then이나 catch 메소드를 사용할 수 있다. 따라서 then 메소드가 Promise 객체를 반환하면(then메소드는 기본적으로 Promise를 반환한다!) 여러개의 프로미스를 연결하여 사용할 수 있다.
new Promise((resolve, reject) => {
setTimeout(function() {
resolve(1);
}, 2000);
})
.then(function(result) {
console.log(result); // 1
return result + 10;
})
.then(function(result) {
console.log(result); // 11
return result + 20;
})
.then(function(result) {
console.log(result); // 31
});
위 코드는 Promise 객체를 생성하여 내부적으로 비동기함수인 setTimeout을 이용, 2초 뒤에 resolve함수를 호출하는 코드이다.
resolve함수가 호출되면, Promise의 상태가 pending상태에서 fulfilled상태로 넘어가면서 그 결과 값인 숫자 1이 첫 번째 then 메서드의 인자로서 넘겨진다. 여기서 숫자 10을 더한 값이 다음 then 메서드의 인자로 넘어간다. 이렇게 메서드가 연결이 되면서 최종적으로 콘솔창에 결과값 31을 출력해준다.
이렇게 콜백함수의 단점을 보완하고자 등장한 Promise객체에도 몇 가지 보완해야할 부분이 존재한다고 한다. 이를 위해 async/await이라는 개념이 등장하였다.
그러나 async/await은 완전히 새로운 개념이 아니라, 기존의 Promise 객체 개념을 베이스로 하여 새로운 기능이 추가된 개념인데 이를 문법적 설탕(syntatic sugar)라고 명칭한다고 한다.
다음 글에서는 async/await 관련 내용을 포스팅 하려고 한다.