➡️ 자바스크립트의 비동기 처리란 특정 코드의 연산이 끝날 때까지 코드의 실행을 멈추지 않고 다음 코드를 먼저 실행하는 자바스크립트의 특성을 의미한다.
console.log("바로 실행됩니다!");
setTimeout(() => {
console.log("2초 뒤에 실행됩니다!");
}, 2000);
console.log("제일 나중에 실행됩니다!");
✳️ 실제 실행결과는 :
"바로 실행됩니다!"
"제일 나중에 실행됩니다!"
"2초 뒤에 실행됩니다!"
➡️ setTimeout() 메서드는 비동기적 web API이기 때문에 가장 나중에 실행된다.
이렇게 특정 로직의 실행이 끝날 때까지 기다려주지 않고 나머지 코드를 먼저 실행하는 것이 비동기 처리이다.
1️⃣ 모든것을 순차적으로 처리해야하는 동기적 처리의 경우에 데이터를 서버에서 받아오는 코드를 최상단에 위치시키다면?
2️⃣ 그리고 그 데이터의 양이 굉장히 많다면?
한없이 기다려야한다...
✳️ 콜백함수란 특정함수의 매개변수로 전달된 함수이다.
예시
const testCallback = function (callback) {
callback();
};
function callback() {
console.log("this is callback")
}
testCallback(callback);
이렇게 함수의 매개변수로 함수를 전달할 수 있고 이때 전달된 함수를 콜백함수라고 한다.
✳️ 콜백함수를 사용하는 이유는?
캡틴판교님의 예시를 보자
다음 예시는 ajax통신을 이용해 서버에서 데이터를 받아오는 코드이다.
우리는 $.get() 메소드를 통해 https://domain.com/ 에서 1번 품목에 대한 정보를 얻어온 뒤에 tableData 라는 변수에 그 정보를 할당하고 싶다.
function getData() {
var tableData;
$.get('https://domain.com/products/1', function (response) {
tableData = response;
});
return tableData;
}
console.log(getData()); // undefined
하지만..
$.get() 메소드는 비동기적 메소드이기 때문에 가장 나중에 실행되고 tableData 에는 undefined 가 할당되게 된다.
(tableData 1번 품목의 정보가 담기지 않는다..)
콜백함수를 이용한다면 우리가 의도한대로 순차적으로 처리할 수 있다.
function getData(callbackFunc) {
$.get('https://domain.com/products/1', function(response) {
callbackFunc(response); // 서버에서 받은 데이터 response를 callbackFunc() 함수에 넘겨줌
});
}
getData(function(tableData) {
console.log(tableData); // $.get()의 response 값이 tableData에 전달됨
});
비동기적 메소드인 $.get()이 실행되고 나서 데이터가 수집되고
그리고 나서 콜백함수를 호출해서 콜백함수의 매개변수에 데이터를 할당한다.
처음 우리가 의도했던대로 데이터를 받아왔다!
하지만 콜백함수의 사용에는 큰 문제가 있다!
다음은 드림코딩 엘리님의 콜백함수 지옥 예시.
console.log(1);
setTimeout(function () {
console.log(2);
}, 1000);
console.log(3);
// 동기 콜백
const printImmediately = function (print) {
print();
};
printImmediately(() => { console.log('hello world!') });
// 비동기 콜백
const printWithDelay = function (print, delay) {
setTimeout(print, delay);
}
printWithDelay(() => { console.log('hello world!') }, 2000);
// callback hell example
class UserStorage {
loginUser(id, password, onSuccess, onError) {
setTimeout(() => {
if (
(id === "ellie" && password === "dream") ||
(id === "coder" && password === "academy")
) {
onSuccess(id);
} else {
onError(new Error("not found!"));
}
}, 2000);
}
getRoles(user, onSuccess, onError) {
setTimeout(() => {
if (user === "ellie") {
onSuccess( {name: "ellie", role: "admin"} );
} else {
onError(new Error("no Sucees!"));
}
}, 1000);
}
getPart(role, onSuccess, onError) {
setTimeout(() => {
if (role === "admin") {
onSuccess ( {part: "develop"} );
} else {
onError(new Error("no Success!"));
}
}, 3000);
}
}
const userStorage = new UserStorage();
const id = prompt("enter your id");
const password = prompt("enter your password");
userStorage.loginUser(
id,
password,
(user) => {
userStorage.getRoles(
user,
(userWithRoles) => {
alert(`Hello! ${userWithRoles.name}, you have a ${userWithRoles.role} role!`);
userStorage.getPart(
userWithRoles.role,
(userWithPart) => {
alert(`Your part is ${userWithPart.part}!`);
},
(error) => {
console.log(error);
}
)
},
(error) => {
console.log(error);
}
);
},
(error) => {
console.log(error);
}
);
✳️ 콜백함수의 문제점
콜백함수 지옥이 발생하게 되면..
1️⃣ 읽기가 너무 거북하다 => 가독성이 떨어진다.
(비즈니스 로직을 한눈에 파악하기 어려움)
2️⃣ 에러가 발생하거나 디버깅을 해야하는 경우에도 너무 어렵다.
(어느 부분에서 에러가 발생하는지 파악 힘듬.)
3️⃣ 유지, 보수도 어렵다.
참고 : 자바스크립트 11. 비동기 처리의 시작 콜백 이해하기, 콜백 지옥 체험 😱 JavaScript Callback | 프론트엔드 개발자 입문편 (JavaScript ES6)
'use strict';
// Promise is JavaScript object for asynchronous operation.
// State : panding -> fulfilled or rejected
// Producer vs Consumer.
// 1. Producer
const id = "ellie";
// doing some Heavy work (ex. network, read files)
const promise = new Promise((resolve, reject) => {
// when new Promise is created, the excutor runs automatically.
// promise 를 만들자마자 기능을 수행해버림 (익스큐터라는 콜백함수가 바로 실행되버림).
// 만약 사용자가 버튼을 누른 순간부터 기능을 수행하고 싶다면? => ??
console.log('doing something!');
if (id === "ellie") {
setTimeout(() => {
resolve('ellie');
}, 2000);
} else {
setTimeout(() => {
reject(new Error('no network!'));
}, 3000)
}
});
// 2. Consumer : then, catch, finally
promise
.then((value) => {
console.log(value);
})
.catch(error => {
console.log(error);
})
.finally(() => {
console.log("end!");
})
// 3. Promise chaining.
const fetchNumber = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 1000);
});
// then은 값을 바로 전달할 수도 있고, promise를 전달할 수 도 있다.
fetchNumber
.then((num) => num * 2)
.then((num) => num * 3)
.then((num) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(num - 1);
}, 1000);
});
})
.then((num) => console.log(num));
// 4. Error Handling.
const getHen = () => {
return new Promise((resolve, reject) => {
setTimeout(() => { resolve('🐓') }, 1000);
});
};
const getEgg = (hen) => {
return new Promise((resolve, reject) => {
setTimeout(() => { reject(new Error(`${hen} => 🥚`)) }, 1000);
});
};
const cook = (egg) => {
return new Promise((resolve, reject) => {
setTimeout(() => { resolve(`${egg} => 🍳`) }, 1000);
});
};
// getHen()
// .then(hen => getEgg(hen))
// .then(egg => cook(egg))
// .then(meal => console.log(meal));
getHen() //
.then(getEgg)
.catch(error => {
return '🥖';
})
.then(cook)
.then(console.log);
드림코딩 엘리님의 Promise 강의 코드.
const id = prompt('enter your id');
const password = prompt('enter your password');
const takeUserId = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (
(id === 'ellie' && password === 'dream') ||
(id === 'coder' && password === 'academy')
) {
resolve(id);
} else {
reject(new Error('input correct "id" or "password"!'));
}
}, 1000);
});
};
const takeUserRole = (userId) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (userId === "ellie") {
const userRole = "admin";
resolve(userRole);
} else if (userId === "coder") {
const userRole = "play";
resolve(userRole);
} else {
reject(new Error('Cant find user role!'));
}
}, 1000);
});
};
const takeUserPart = (userRole) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (userRole === "admin" || userRole === "play") {
const userPart = 'development';
resolve(console.log(`Your part is ${userPart}!`));
} else {
reject(new Error('dont find your role!'));
}
}, 1000);
});
};
takeUserId() //
.then(takeUserRole)
.then(takeUserPart);
프로미스를 활용해서 이전예시 콜백지옥을 해결한 모습
참고 : 자바스크립트 12. 프로미스 개념부터 활용까지 JavaScript Promise | 프론트엔드 개발자 입문편 (JavaScript ES6)
출처 : 비동기 프로그래밍 | JavaScript로 만나는 세상
[JS] 비동기(async) 프로그래밍 이해하기[1]. JS 비동기 패러다임 소개 | by Kwoncheol Shin | Medium
자바스크립트 비동기 처리와 콜백 함수 • 캡틴판교
자바스크립트의 비동기 작업 처리방식
1. 동기와 비동기, 콜백함수
2부 - 자바스크립트 비동기적 프로그래밍(콜백, 프라미스)