동기 : 동시에 일어나는 것
비동기 : 동시에 일어나지 않는 것
자바스크립트는 싱글쓰레드(일꾼이 하나인것) ↔ 멀티쓰레드(자바, GO)
너는 나랑 동기야 (같이 간다)
→ 동기는 요청을 보낸 후 결과를 받아야만 다음 동작이 이루어지는 것
너는 나랑 동기가 아니야 (너는 너 할일 해 나는 내 할일 할게)
→ 효율성을 위해 비동기는 요청을 보낸 후 결과를 받지 않아도 다음 동작이 이루어지는 것
동기와 비동기의 차이
동기는 요청을 보낸 후 결과를 받아야만 다음 동작이 이루어는 것이다. 비동기는 효율성을 위해 요청을 보낸 후 결과를 받지 않아도 다음 동작이 이루어지는 것이다. 자바스크립트는 멀티 스레드가 아닌 싱글 스레드이기 때문에 한번에 한가지 일 밖에 처리할 수 있다. 따라서, 시간이 소요되는 작업을 할 때는 해당 작업을 진행한 후 실행을 해야 하는데 이러한 작업들을 비동기를 통해 효율적으로 운용할 수 있습니다. 비동기는 자바스크립트가 실행될 때
이벤트 루프
를 통해 실행되며 비동기 처리가 끝난 후 실행되어야 할 함수들은이벤트큐(태스크 큐)
에 저장해두었다가 비동기가 끝난 후 차례대로 실행한다. 즉, 이벤트 루프에서는 이벤트 발생 시 호출되는 콜백 함수들을 테스크 큐에 전달하고, 테스트 큐에 담겨 있는 콜백함수들을콜스택
에 넘겨준다.
이벤트 루프
function main(){ console.log('First'); setTimeout( function display(){ console.log('Second'); } ,0); console.log('Third'); } main(); // Output // First // Third // Second
'First'와 'Third' 2개의 console.log와 그 사이에 0ms 의 대기 시간으로 콘솔에 'Second'를 보여주는 setTimeout함수가 끼워져 있습니다. 결과값을 보게 되면 First => Second => Third 순서가 아닌 First => Third => Second 순서로 나오게 되는데 아래 그림과 함께 살펴보도록 하겠습니다.
main 함수에 대한 호출이 먼저 call stack에 추가(push)됩니다. 그런 다음 브라우저는 main 함수의 첫 번째 명령문인 console.log('First')를 스택으로 추가됩니다. console.log('First')가 실행되어 화면에 출력한 뒤, call stack에서 제거됩니다.
다음 명령문인 setTimeout이 call stack에 추가되고 실행이 되면서 브라우저가 제공하는 timer Web API를 호출한 후에 call stack 에서 제거됩니다.
- console.log('Third')는 브라우저에서 timer가 실행되는 동안 스택에 추가가 되며 이 경우 0ms 의 시간만큼 지연이 되기 때문에 브라우저에서 콜백을 수신하는 즉시 메시지 대기열에 콜백이 추가됩니다.
- main함수에서 마지막 명령문이 실행된 후 main 함수가 call stack 튀어나와 call stack 이 비어있게 됩니다. 브라우저가 대기열인 message queue 에서 call stack 으로 메시지를 추가하려면 먼저 call stack이 비워있어야 합니다. 바로 이것 때문에 setTimeout에서 제공된 지연시간이 0ms임에도 다른 모든 것들이 실행이 완려될 때까지 기다려야 하는 이유입니다.
- 이제 콜백이 call stack으로 추가되어 실행이 됩니다. console.log('Second')가 실행이 되며 이것이 자바스크립트의 이벤트 루프 입니다.
이벤트큐
이벤트 큐에서는 비동기 작업들이 실행된 후 호출되는 콜백함수들을 기다리는 공간이다. 이벤트 루프가 정해진 순서대로 줄을 서 있으며,
FIFO
선입선출이다. 하나의 큐로 이루어져 있지 않으며 여러개의 큐로 이루어져 있다.
넘겨준 함수를 해당 밀리초만큼 최소 시간을 보장 호출 / 비동기 함수
setTimeout(() => console.log("test"), 1000)
(1000 = 1초) → 여기서 시간이 걸리는게 비동기다.
setTimeout에 등록된 이벤트를 취소
넘겨준 함수를 최소 해당 밀리초만큼 보장한 후 매번 호출
setInterval에 등록된 이벤트를 취소 / 비동기 함수
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>setTimeout</title> </head> <body> <div id="select">저를 선택해주세요</div> <button onclick="stopSetTime()">셋타임 취소</button> <button onclick="stopSetInterval()">셋인터벌 취소</button> <button onclick="reSetTime()">다시시작</button> <script> const select = document.getElementById("select"); console.log(select); function selectPrint() { console.log(select.innerText); // select.style.backgroundColor = "red"; } const setTimePrint = setTimeout(selectPrint, 3000); // 콘솔이 3초마다 계속 찍힘 let setIntervalPrint = setInterval(selectPrint, 3000); function stopSetTime() { clearTimeout(setTimePrint); } function stopSetInterval() { clearInterval(setIntervalPrint); } function reSetTime() { setInervalPrint = setInterval(selectPrint, 3000); } </script> </body> </html>
매개변수로 함수를 전달받아서, 함수 내부에서 그 함수를 실행하는 경우
용도는 자바스크립트에서 순차적으로 실행하고 싶을 때 사용한다.
const callback = (number, callback) => { number = number + 1; console.log(number); callback(); }
버튼을 클릭했을 때 안에있는 것을 실행해준다.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>callback</title> </head> <body> <script> function first(파라미터) { // 3. 콘솔로그 실행 console.log(1); // 4. 파라미터에 들어온 second함수가 여기서 실행 파라미터(); } function second() { console.log(2); } // 1. first를 실행해주세요 // 2. 파라미터 자리에 second함수를 넣어주세요 first(second); </script> </body> </html>
## 콜백지옥
js는 비동기를 마주하는 상황이 빈번하게 일어남
백엔드에서 결과 값을 받아오기 전에 어떠한 로직을 실행하게 되면 에러 발생
비동기 로직을 동기적으로 처리할 필요가 생김
이러한 동기적 처리를 위해 과거 많이 사용하던 로직이 콜백함수
그러나, 이러한 콜백함수가 중첩되어 함수의 길이가 깊어지고(계단식) 가독성이 떨어진다.
> **콜백 예제**
```js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>callback</title>
</head>
<body>
<script>
const setTimeOutPrint = (number, callback) => {
setTimeout(() => {
console.log(number);
>
callback(number);
}, 3000);
};
>
function addnumb(number) {
console.log(number + 5);
}
const printNum = (number, callback) => {
console.log(number);
callback();
};
>
// 로직이 실행
// 콜백이 계속 늘어난다 = 콜백지옥
setTimeOutPrint(5, printNum(5, addnumb));
</script>
</body>
</html>
비동기 통신 처리를 위하여 악명 높은 콜백지옥을 탈출시켰다는 점에서 굉장히 높은 평가를 받고 있다.
promise는 대기상태(pending
), 성공상태(resolve
), 실패상태(reject
)
promise를 선언하는 순간 대기 상태에서 실행되어 있지만 나중에 어떠한 조건을 만족했을 때 결과 값을 받는 객체
promise는 항상 promise 타입의 객체를 return(반환)
객체 안의 then(성공) catch(실패)
단, promise 또한 callback 함수에 기반을 두고 있다는 점에서 한계가 있다. 로직이 또 길어진다. 따라서 자바스크립트에서는 이러한 promise의 한계점을 해결하기 위해서 async await
를 도입 [= async, await, promise]
Promise함수 예제
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>promise</title> </head> <body> <script> // 비동기 흐름 뒤에 써야만 하는 로직 // 내부 함수는 중요하지 않다. const callback = (number) => { setTimeout(() => { number = number + 1; console.log(number); // 7 }, 1000); return number; }; let a = 5; // pr이라는 상수를 만들고 Promise라는 객체를 담아주었고 // 성공했을때와 실패했을 때 이 두가지 값을 리턴받는다. const pr = new Promise((resolve, reject) => { setTimeout(() => { a = a + 1; // 1. resolve는 성공상태이고 reject는 실패상태이다. // 만약에 if문을 setTimeout 밖에 있다면 실패가 뜬다. // 그 이유는 setTimeout은 비동기 이기 때문에 // 따로 빼서 이벤트루프로 보내주고 이벤트 큐에 넣어주는데 // 큐는 FIFO(선입선출)이다. 그렇기때문에 따로 있으면 // if문이 먼저 실행이 되서 실패한다. if (a === 6) { resolve(a); // 6 } else { reject("실패"); } }, 1000); }); // 위에 있는 Promise 값을 then과 catch를 통해서 받아올 수 있다. pr.then((result) => { console.log(result); // 6 return callback(result); // 7 // reject(실패)를 받아온다. }).catch((err) => { console.error(err); // 실패 }); </script> </body> </html>
결국에 콜백에 기반을 두고 있는 promise는 then catch의 반복으로 코드가 길어지는 현상이 생긴다는 것은 다를 것이 없다.
따라서 이러한 promise의 한계를 해결하기 위해 등장했다.
async가 사용된 함수를 promise형태로 만들어주며 하위에 await이란 객체
await 앞의 함수가 실행될 때까지 promise를 잠시 대기 시켜놓습니다.
async 예제1
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>async</title> </head> <body> <script> // 3. 5를 받는다. const callback = (number) => new Promise((resolve, reject) => { setTimeout(() => { number = number + 1; // 5. 위에서 5 + 1해서 3초 후 6이 나온다. console.log(number); // 6. 성공했을 때 return해줌 3초 후 6 이 return resolve(number); }, 3000); }); // const asynEx = async (number) => { // try { // // 1. number 5를 받아와서 5가 찍힌다. // console.log(a); // // 2. callback으로 5를 넘겨준다. // // 7. resolve에서 6을 then으로 받고 콘솔로 찍어줌 // callback(a).then((result) => console.log(result)); // // 4. 5 + 2 = 7이 찍힘 // console.log(a + 2); // } catch (error) { // console.log(err); // } // }; /* 콘솔 : 5 7 6 6 */ // await을 활용함으로써 // 비동기를 동기적 흐름으로 만들어주고 // 콜백이나 then이 없어도 해당 비동기 함수 실행 후 // 다음 함수를 실행시킬 수 있다. const asynEx = async (number) => { try { console.log(number); // await가 위에서 실행하는 비동기를 기다렸다가 아래께 실행한다. // 비동기 → 동기처럼 실행하는 것이다. let result = await callback(number); console.log(result); } catch (error) { console.log(err); } }; /* 콘솔값 5 6 7 */ let a = 5; asynEx(a); </script> </body> </html>
async 예제2
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> </head> <body> <script> // function 앞에 async를 붙여줍니다. // promise 객체 앞에 await를 붙여줍니다. // async가 붙은 함수는 promise 객체를 반환합니다. 따라서 .then((a) => {})를 이용할 수 있다. function delay() { return new Promise((resolve) => { setTimeout(() => resolve(), 1000); }); } async function getApple() { await delay(); return "apple"; } async function getBanana() { await delay(); return "banana"; } function getFruites() { // 리턴값이 promise의 resolve()이므로 then가능 getApple().then((a) => { getBanana().then((b) => console.log(`${a} and ${b}`)); }); } getFruites(); </script> </body> </html>
async의 예외처리
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> </head> <body> <script> // async은 throw로 예외처리를 할 수 있다. async function p2() { throw "error"; } p2() .then((n) => console.log(n)) .catch((n) => console.log(n)); </script> </body> </html>
요청
백엔드 게시물 정보 요청을 보내면 시간이 지난 후(비동기) 프론트엔드에 데이터를 전달한다.받기
데이터를 사용하려고 일반적으로 실행하게 되면 동기 흐름으로 처리되기 때문에 해당 데이터 받기전에 사용되서 사용이 불가능하다.이러한 문제점을 해결하기 위해서 비동기처리(
콜백함수
,promise
,async/await
)를 하게되는 것콜백함수는 비동기함수가 끝나면 매개변수로 전달된 콜백함수가 실행되는 형태 또한 그 이후에 샐행되는 함수들도 모두 콜백으로 전달
promise는 콜백으로 전달하지 않고
.then
,.catch
이라는 로직으로 비동기가 실행 후 실행해야할 함수를 실행async/await는 then도 콜백도 필요없이 awai을 통해 해당 비동기를 기다렸다가 비동기 로직 이후 실행될 함수들을 동기적으로 작성해도 실행한다. 장점은 콜백이나 then처럼 코드가 깊어지지 않는다.