콜백함수 패턴은 자바스크립트에서 비동기적으로 순서를 제어하고 싶을 때 사용하는 콜스택 중 하나이다. 콜백을 사용함으로서 특정함수의 동작이 끝나면, 바로 다른 여러가지 함수를 호출할수 있게 만든다.
유투브를 예로 들어보자. 만약 유투브에서 모든 기능들이 동기적으로 이루어진다 가정했을때, 영상을 보며 댓글을 달고 만약 그 댓글이 올라가는데 1분이 걸린다면 우리는 1분동안 화면에서 영상을 보지 못하고 가만히 기다리고 있어야 할 것이다. 따라서 이러한 현상을 방지하기 위해 UI를 비동기적으로 응답을 받게한다.
setTimeout
함수는 브라우저에서 제공하는 대표적인 Web API이다.
const say = (A)=> {
setTimeout( () => { console.log(A) }, Math.floor(Math.random() * 100) + 1)
}
const printAll = () => {
say('Hello')
say('From now on I\'m your friend!!')
say('Good bye')
}
printAll()
만약 printAll()
을 이용하여 세 함수를 실행시킨다면 setTimeout함수로 인해 순서대로 console에 출력되지 않을 것이다. 순차적으로 console을 출력하기 위해서는 callback함수를 이용하여 순서를 넘겨주어야 한다.
const say = (A, callback)=> {
setTimeout( () => { console.log(A)
callback() }, Math.floor(Math.random() * 100) + 1)
}
const printAll = () => {
say('Hello', () => {
say('From now on I\'m your friend!!', () => {
say('Good bye', () => {})
})
})
}
printAll()
// 'Hello'
// 'From now on I\'m your friend!!'
// 'Good bye'
또한 함수들이 callback시 실패할 수도 있기 때문에 실패에 대한 함수를 작성해야 한다
const somethingGonnaHappen = ((err, data) => {
if (err) { // 에러가 있는경우
console.log('ERR!!');
return;
// 에러 처리
}
return data;
// 아닐 시 callback
})
callback을 사용할 시 가장 큰 단점은 개발 시 모든 과정을 비동기 처리해야 한다면 코드가 보다 복잡해지고 가독성이 저하된다(콜백 지옥). 따라서 콜백지옥을 해결하기 위해 ES6에서 Promise가 제공되었다.
Promise는 promise 자체 생성자 함수를 통해 new Promise로 인스턴스화 한다(마치 class처럼). 생성자는 인스턴스로 resolve, reject를 받으며 각각 함수로 적용된다. resolve는 비동기 작업이 성공할 시 결과값을 도출해내는 함수이고, reject는 비동기 작업이 실패할 시 오류의 결과값을 도출해낸다.
// Promise 객체의 생성
const promise = new Promise((resolve, reject) => {
// 비동기 작업을 수행한다.
if (/* 비동기 작업 수행 성공시 */) {
resolve('result');
}
else { /* 비동기 작업 수행 실패시 */
reject('error');
}
});
Promise로 구현된 비동기 함수는 Promise 객체를 반환하여야 하며then
이라는 객체 후속 처리 메소드를 이용하여 콜백으로 비동기 작업이 직행 될 수 있다. then 메소드를 이용할 시 처음 function을 실행 후 작업이 완료되면 then을 이용해 다음 function을 실행되어 작업을 진행하게 만든다.
function say() {
return new Promise((resolve, reject) => {
setTimeout(function () {
resolve("Hello");
}, Math.floor(Math.random() * 100) + 1)
})
}
function introduce() {
return new Promise((resolve, reject) => {
setTimeout(function () {
resolve("From now on I\'m your friend!!");
}, Math.floor(Math.random() * 100) + 1)
})
}
function bye() {
return new Promise((resolve, reject) => {
setTimeout(function () {
resolve("Good bye");
}, Math.floor(Math.random() * 100) + 1)
})
}
say()
.then(data => {
console.log(data)
return introduce()
})
.then(data => {
console.log(data)
return bye()
})
.then(data => {
console.log(data)
})
Promise의 장점 중 하나는 에러를 쉽게 처리 할 수 있다는 것인데, Promise 객체의 후속 처리 메소드 catch을 사용하여 promise의 reject에서 받은 오류를 처리 할 수 있다.
function getData() {
return new Promise(function (resolve, reject) {
reject(new Error("Request is failed"));
});
}
// reject()의 결과 값 Error를 err에 받음
getData().then().catch(function (err) {
console.log(err); // Error: Request is failed
});
[Async & Await 전 promise.all 이해하기]
Async & Await은 기존의 비동기 처리 방식인 콜백 함수와 프로미스의 단점을 보완해 줄 수 있는 비동기 처리 패턴이다.
await는 async함수가 resolve 될때까지 기다릴 수 있다. 즉 promise의 값이 반환, 사용 가능할때까지 함수의 실행을 중지 시킨 후 resolve 된다면 반환하게 만들어준다.
function say() {
return new Promise((resolve, reject) => {
setTimeout(function () {
resolve("Hello");
}, Math.floor(Math.random() * 100) + 1)
})
}
function introduce() {
return new Promise((resolve, reject) => {
setTimeout(function () {
resolve("From now on I\'m your friend!!");
}, Math.floor(Math.random() * 100) + 1)
})
}
function bye() {
return new Promise((resolve, reject) => {
setTimeout(function () {
resolve("Good bye");
}, Math.floor(Math.random() * 100) + 1)
})
}
const printAll = async () => {
const one = await say();
console.log(one)
const two = await introduce();
console.log(two)
const three = await bye();
console.log(three)
}
printAll();