* 비동기 (asynchronous)의 개념
프로그램이 순차적으로 실행되다가 비동기 코드를 만나면 해당 코드는 나중에 실행하라고 등록만 해놓고 다음 부분을 먼저 실행하는 것을 비동기라고 한다. 회사에서 업무를 처리하다가 시간이 오래 걸리는 작업은 후임에게 맞겨놓고 자기는 그 다음 일을 처리한 다음 후임이 해당 작업을 끝내면 보고 받는 식이다.
* 비동기 (asynchronous)를 사용하는 이유와 활용
비동기의 대표적인 예는 아래와 같이 서버에서 데이터를 가져오는 등의 시간이 오래 걸리는 작업이다.
const API_URL = 'http://makeup-api.herokuapp.com/api/v1/products.json?brand=maybelline'
fetch(API_URL)
.then(function (res) {
return res.json()
})
.then(function (products) {
console.log(products)
})
function add(a, b) {
return a + b
}
console.log(add(3, 4))
위 코드는 서버의 특정 URL 주소로부터 데이터를 조회한다. fetch 는 비동기 함수이기 때문에 서버에서 데이터를 가져오라고 등록만 해놓고 add 함수를 먼저 실행한다. 그런 다음 서버에서 데이터를 가져왔을때 콜백함수가 실행되면서 데이터를 조회하게 된다.
console.log('안녕하세요 !')
setTimeout(function () {
console.log('나중에 봐요 ~')
}, 3000)
console.log('반가워요 !')
위 코드는 setTimeout 이라는 브라우저 API 를 사용한 예제이다. 위 코드가 만약 동기적으로 실행된다면 첫번째 메세지가 출력되고, 3초 기다렸다가 두번째 메세지가 출력된 다음 마지막에 세번째 메세지가 출력되어야 한다. (동기방식인) 하지만 실제로는 setTimeout 함수가 비동기로 동작하기 때문에 등록만 해놓고 나중에 실행이 된다. 출력 결과는 아래와 같다. (비동기 방식)
* 함수 콜스택 (Call Stack)
function getFirst() {
throw new Error('stop')
}
function getSecond() {
getFirst()
}
function getThird() {
getSecond()
}
getThird()
함수 콜스택은 함수의 호출 순서를 기억하는 자료구조이다. 스택(stack)은 자료구조에서 PUSH 와 POP 으로 동작한다. 자바스크립트 엔진은 함수의 호출 순서에 따라 함수를 스택에 PUSH 한다. 함수를 순서대로 쌓았으면 다시 맨 위에 놓여있는 함수부터 POP 하면서 실행한다.
주로 프로그램을 구현하다가 오류가 난 경우 함수 콜스택을 콘솔에 출력해준다.
위 코드에서 보면 getThird 함수가 맨 먼저 호출되므로 콜스택의 맨 아래에 PUSH 된다. getThird 함수 내부에서 getSecond 함수가 호출되므로 콜스택에 두번째로 PUSH 된다. 마지막으로 getSecond 함수 내부에서 getFirst 함수가 호출되므로 콜스텍의 맨 위에 PUSH 된다. 더이상 호출할 함수가 없으므로 getFirst 함수를 POP 하여 실행한다. 이때 에러가 발생한다.
만약 getFirst 함수에서 에러가 발생하지 않는다면 getFirst 함수를 콜스택에서 POP 하여 실행한 다음 두번째에 놓여진 getSecond 함수를 POP 하여 실행하고, 마지막으로 맨 아래에 놓여진 getThird 함수를 POP 하여 실행하게 된다.
function getFirst() {
console.log('stop')
}
function getSecond() {
getFirst()
}
function getThird() {
getSecond()
}
debugger
getThird()
debugger 키워드를 사용하면 프로그램이 디버깅 모드로 동작하게 된다. 크롬 개발자 도구에서 Source 탭의 Call Stack 메뉴를 확인해보자! 아래쪽 화살표를 클릭하면 콜스택에 함수가 쌓이는 순서를 확인할 수 있다.
* 콜백지옥(Callback hell) - 비동기 처리를 위한 콜백함수 사용시 문제점
function getAPIData(url, callback) {
console.log('fetching data from api ...')
const products = ['paper', 'cosmetic', 'cup', 'glass', 'computer', 'calendar']
callback(products) // 첫번째 콜백함수에 서버에서 조회한 상품정보 전달
}
function filterProducts(products, callback) {
console.log('filtering products ...')
callback(products.filter(product => product.length > 5)) // 두번째 콜백함수에 필터링된 상품정보 전달
}
function sortProducts(products, callback) {
console.log('sorting products ...')
callback(products.sort()) // 세번째 콜백함수에 정렬된 상품정보 전달
}
function printResult(result) {
console.log(result)
}
// 콜백지옥
getAPIData('https://google.com', function (products) { // 서버에서 조회한 상품정보
filterProducts(products, function (products) { // 필터링된 상품정보
sortProducts(products, function (result) { // 정렬된 상품정보
printResult(result) // 결과 출력
})
})
})
위 코드는 서버로부터 가상으로 API 데이터를 조회하고, 조회 결과로부터 필터링과 정렬을 거친 결과를 출력하는 기능을 연속적인 콜백함수로 구현한 것이다. 이를 콜백지옥 (Callback hell)이라고 한다.
getAPIData 함수는 url 주소를 이용하여 API 서버로부터 상품 정보를 조회한 다음 해당 데이터를 첫번째 콜백함수의 인자로 전달한다. filterProducts 함수는 서버에서 가져온 데이터를 이용하여 조건에 맞는 상품들만 필터링한 다음 두번째 콜백함수의 인자로 전달한다. sortProducts 함수는 필터링된 상품정보를 이용하여 오름차순으로 정렬한 다음 세번째 콜백함수의 인자로 전달한다. 마지막으로 printResult 함수는 필터링과 정렬이 끝난 상품정보를 콘솔 화면에 출력한다.
연속적인 콜백함수는 비동기 처리를 순차적으로 실행해야 할때 사용하지만 코드 가독성을 떨어뜨리고 코드 구조를 변경하기 힘들다. 아래와 같이 콜백함수들을 따로 분리하면 콜백지옥을 피할수는 있다.
function getAPIData(url, callback) {
console.log('fetching data from api ...')
const products = ['paper', 'cosmetic', 'cup', 'glass', 'computer', 'calendar']
callback(products) // 첫번째 콜백함수에 서버에서 조회한 상품정보 전달
}
function filterProducts(products, callback) {
console.log('filtering products ...')
callback(products.filter(product => product.length > 5)) // 두번째 콜백함수에 필터링된 상품정보 전달
}
function sortProducts(products, callback) {
console.log('sorting products ...')
callback(products.sort()) // 세번째 콜백함수에 정렬된 상품정보 전달
}
function printResult(result) {
console.log(result)
}
// 콜백함수 분리
function getAPIDataDone(products){
filterProducts(products, filterProductsDone)
}
function filterProductsDone(products){
sortProducts(products, sortProductsDone)
}
function sortProductsDone(result){
printResult(result)
}
// 콜백지옥
getAPIData('https://google.com', getAPIDataDone)
자바스크립트에서는 이러한 콜백지옥을 해결하고 비동기 처리를 좀 더 효율적으로 처리하기 위하여 Promise 와 Async 문법을 제공한다.
* 프로미스 (Promise)
const $ = {
get(url, callback){
httpRequest = new XMLHttpRequest()
httpRequest.open('GET', url, true)
httpRequest.send(null)
httpRequest.onreadystatechange = function(){
if (httpRequest.readyState === XMLHttpRequest.DONE) {
if (httpRequest.status === 200) {
callback(JSON.parse(httpRequest.responseText))
} else {
console.log('서버에서 응답을 가져오는데 실패하였습니다.')
}
} else {
console.log('아직 응답을 받을 수 없습니다.')
}
}
}
}
const API_URL = 'http://makeup-api.herokuapp.com/api/v1/products.json?brand=maybelline'
$.get(API_URL, function(response){
console.log(response)
})
위 코드는 제이쿼리 라이브러리를 사용하지 않고 순수 자바스크립트로 AJAX 콜 기능을 구현한 것이다. 즉, 자바스크립트의 XMLHttpRequest 객체를 사용하여 웹페이지 새로고침 없이 서버와 통신하는 기능이다. 서버로부터 받은 응답 결과는 콜백함수의 인자로 전달되어 response 를 출력한다. 아래와 같이 화장품에 대한 상품목록을 서버로부터 가져온다.
비동기와 이벤트루프
setTimeout, setInterval
비동기 (fetch, axios)
'프론트엔드 > Javascript' 카테고리의 다른 글
자바스크립트 문법 3 - 이벤트(Event) 처리하기 (0) | 2021.11.30 |
---|---|
에러 처리 (Error handling) (0) | 2021.10.09 |
자바스크립트 문법 2 - 브라우저에서의 자바스크립트 (0) | 2021.10.09 |
자바스크립트 문법 6 - 배열 (Array) 의 기본 (0) | 2021.10.09 |
자바스크립트 문법 11 - 객체 (Object) (0) | 2021.10.09 |