일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- 채팅
- dict
- Class
- docker
- wetube
- SAA
- SSA
- 중급파이썬
- pandas
- AWS
- RDS
- MongoDB
- git
- react
- merge
- node
- TypeScript
- crud
- 튜플
- 파이썬
- Props
- 카톡
- socket io
- async
- S3
- NeXT
- flask
- Vue
- EC2
- lambda
- Today
- Total
초보 개발자
동기 비동기에 대한 고찰.... 본문
동기 비동기 💢
내가 생각한 동기 비동기는 차례대로 코드들을 읽다가(동기) 어떤 비동기적인 코드를 읽으면 '동시'에 동기코드를 처리해나가면서 비동기 코드또한 처리를 해나간다고 생각해왔다. (옆 화구에 백숙(비동기)'!올려놓고!' 라면(동기) 끓이기 )
근데 스레드가 하나(일하는 사람이 하나)라면 동시에 여러 일을 진행 시킬 수 없으니까. 이건 말이 안된다고 한다.
따라서 비동기는 '동시'에 같이 일을 진행시키는 것이 아니라 일단! 뒤로 미뤄두고
동기코드가 다 끝나면 그제서야 미뤄두었던 일들을 시키는 것이다.
생각해보면 동기 비동기로 나누는게 당연한 것이다.
왜냐하면 내부가 아닌 외부에서 정보를 받아오는데 받아올 때 까지 주구장창 기다린다면
그 다음 코드들로 넘어가지 않으니까말이다??
(근데 여기서 궁금한게 어떤게 비동기이고 어떤게 동기인지, 어떻게 구분하고 아는건지..? 모르겠다.. 예를들어 이건 비동기니까 async await을 써야지!!라고 생각하는 것 말이다.)
--> 메인함수가 다 끝난 뒤 eventloop에 의해 callback que에 들어있는 비동기 코드 하나씩 callstack 으로 불러와서 실행
의문점 1 비동기 안에 비동기?❔
비동기코드 A, B가 있고 A안에 또 비동기코드 C가 있다고 가정해보자
A가 실행이 되고 비동기코드 C를 만났다.
의문점 1. 그렇다면 callstack [A] callback que [ B, C] 이렇게 B뒤에 C가 생기는가? 따라서 A가 끝나고 C를 실행시키는 것이 아니라. B를 실행시킨뒤에 C를 실행시키는가?
console.log('1')
setTimeout(() => {
console.log('2')
setTimeout(() => {
console.log('3')
},1000)
},1000)
setTimeout(() => {
console.log('4')
},1000)
이런 코드를 실행시켰을 때 1 2 4 3 으로 출력되는 것으로 보아 질문 1대로 실행이 되는 것 같다.
이어서 이야기 해보자,
비동기하면 빼먹을 수 없는 setTimeout이다 예를들어서
console.log('1')
settimeout(()=>{console.log('2')},0) # 0초 뒤에 () => console.log('2')를 callback que에 넣어!
console.log('3')
이러한 코드가 있다면 1 3 찍히고 2가 출력이 될 것이다. setTimeout은 비동기 함수이기 때문에 메인함수가 다 끝난 다음에 실행이 되기 때문이다.
근데 우리가 1 2 3 순으로 출력을 시키고 싶다면, 당연하겠지만 console.log('2')를 찍고 나서 console.log('3')을 찍고 싶다면 콜백함수에 단순히 추가만 해주면 된다.
console.log('1')
settimeout(()=>{
console.log('2')
console.log('3')
},0)
근데 여기서 단순히 console.log가 아닌 콜백함수에 또 비동기 함수 setTimeout을 사용할 수 있다.
console.log('1')
settimeout(()=>{
console.log('2')
settimeout(()=>{
console.log('3')
},0)
},0)
이런식으로 콜백함수에 콜백함수가 이어지는 것을 콜백 지옥이라고 한다. 단순히 하나만 추가했는데 벌써부터 어질어질하다.
Promise🧡
따라서 이 복잡성을 해결하기 위해서 Promise라는 것이 나왔다.
Promise는 pending fullfilled rejected 이 3가지로 이루어져있다고 한다.
- Pending(대기) : 비동기 처리 로직이 아직 완료되지 않은 상태
- Fulfilled(이행) : 비동기 처리가 완료되어 프로미스가 결과 값을 반환해준 상태
- Rejected(실패) : 비동기 처리가 실패하거나 오류가 발생한 상태
자바스크립트 Promise 쉽게 이해하기 • 캡틴판교 (joshua1988.github.io)
콜백 함수 안에 콜백함수를 넣는 것이 아닌 프로미스객체.then() 형식으로 fullfilled 상태가 반환되면 then()안에 비동기 코드를 넣어서 훨씬 수월하게 비동기안에 비동기를 처리할 수 있게 되었다고 한다.
resolve가 실행 되는 시점은 외부에서 데이터를 받아온 값이 정상일 때다. 받아올 때 까지 콜백함수는 기다리는 듯하다.
비동기의 실행순서가 궁금해서 위와같은 실험을 해보았다. 아래와 같은 결과가 출력이 되었는데
먼저 '--------1---------'이 출력이 되고 그 다음 getData()가 실행이 되어 그 안에 있는 console.log가 찍힌 것이 보인다.
이 때 fulfilled이 되고 then을 만나면 여기서 다시 비동기로 코드가 실행이 되어지는 것 같다.
따라서 callback que에는 then(getData)가 들어 있어 아래와 같은 상황일 것이다.
callback que [then(getData)]
이 후에 '------------2------------'를 지나고 다시 getData()를 만나서 callback que에는 아래와 같은 상황일 것이다.
callback que [then(getData), then(console.log) ]
이제 메인 함수가 끝이 났으니 callback que에서 하나씩 stack으로 가져와 실행을 시킬 것이다.
앞에 있는 then(getData)이 stack으로 올라가서 실행이 되고 그 안의 내용을 다시 then(getData)를 비동기로 실행시킨다.
따라서 callback que에는 아래와 같은 상황일 것이다.
callback que [ then(console.log), then(getData) ]
이제 stack에 then(console.log)가 들어가고 출력을 한 뒤에 then이 그 뒤에 없으니 다시 비동기 처리를 해줄 게 없다. 따라서 여기서 종료.. 그래서 1이라는 것이 콘솔에 찍힌 것이다. 이 후 callback que에는 아래와 같은 상황일 것이다.
callback que [ then(getData) ]
stack에 then(getData)가 들어가고 거기서 then.(console.log)를 que에 집어 넣는다.
callback que [ then(console.log) ]
이제 이 뒤로 then이 없으니 다시 비동기 처리를 해줄 게 없다 3이 찍히고 끝이 나게 된다.
여기서 느낀게 나는, promise를 사용하면.
console.log('--------1--------')
getData().then(getData).then(getData).then(console.log)
console.log('--------2--------')
getData().then(console.log)
마지막 줄에 있는 then(console.log)는 '--------1---------'과 '---------2------------'사이에 있는 promise chain이 끝이 난 후에 실행이 된다고 생각을 했다.. 즉 콘솔에 3이 먼저 찍히고 그 다음에 1이 찍힐 것이라고 예상을 했다.
연쇄적인 비동기코드 들을 동기처럼 실행시킨다고 생각했다.
하지만 실제로 실험을 해본 결과 비동기 코드를 만날 때 마다 que 뒷자리에 집어 넣는 것 같다.
그리고 프로미스 객체 자체는 비동기로 처리 되는게 아닌 거같고 then을 사용하였을 때 비동기로처리가 되는 것 같다.
아무튼 promise를 사용하면 이렇게 then을 사용하여 콜백 지옥을 피할 수 있게 되었지만 then이 계속이어지면 이 또한 지옥이 되어버린다고 한다 따라서
promise의 sugar syntax에 해당하는 async await이라는 것이 나왔다. 어떤 함수 앞에 async를 붙이면 promise를 반환하고 그 안에서 promise객체를 반환하는 비동기함수 앞에는 await을 붙일 수 있다. await을 사용하면 그 비동기가 어떤 값을 리턴할 때 동안 그 뒤로 넘어가지 않는다. ( await를 붙이지 않았다면 비동기 함수이니까 callback que에 함수를 넣어두고 그 다음 코드를 읽음 )
따라서 async await을 사용하면 비동기 함수를 동기화를 시켜준다고 생각한다.
즉 async await의 역할은 직관적으로 비동기 함수를 동기 처리할 수 있도록 해주는 것이다. ( 맞나요 ? )
import fetch from 'node-fetch';
const jebal = async () => {
const first = await (await fetch('https://jsonplaceholder.typicode.com/users/1')).json()
console.log('-----------------------------------------first-------------------------------')
console.log(first)
const second = await (await fetch('https://jsonplaceholder.typicode.com/users/2')).json()
console.log('-----------------------------------------second-------------------------------')
console.log(second)
}
jebal()
순서대로 잘 처리되어진 것을 볼 수 있다.
예제를 살짝 바꿔서 아까 promise에서 사용했던 걸 다시 사용해보자
aynsc await을 사용하면 전에 내가 promise에서 예상했던 실행순서,
즉, 연쇄적인 비동기코드들을 동기처럼 실행시키는 것이 확인이 된다.
then으로 이루어진 promise chain들은 비동기 코드를 만나면 que의 맨 뒷자리로 이동시키지만,
async await으로 이루어진 것들은 비동기 코드를 만나면 que로 보내지 않고 다 끝날 때 까지 기다린 후 다시 그 다음 비동기 코드를 동기처럼 실행시켜준다.
이렇게 하고 끝내려고 했다만.....
의문점 2 실행순서?❔
나는 자연스럽게 async 내의 함수는다 동기적으로 실행이 된다고 생각을 했다. 근데 반은 맞고 반은 틀린 것같다??
아래의 코드를 실행시켜보고 나로썬 받아들이기 어려운 충격적인 결과가 나타났다...🔞
하나씩 천천히 살펴보면 async내에 함수가 동기로 하나 하나 실행이 되다가 await을 한번 만나는 순간 바로 일시정지 해버린다. que에다가 await안에 있는 함수를 집어 넣는 것 같다. 그리고 async함수의 밖으로 빠져나가 그 다음 함수를 차례로 실행시킨다. 그리고 main함수가 끝이 나는 순간 (---------네 번 째 콘솔 --------- 이 찍힌 후)
현재 que에는 [getData(20) getData(10) getData(40)]이 들어있을 것이다.
제일 먼저 getData(20)이 stack으로 올라가서 뒤에 1을 더한 후에 다시 한번 await을 만나 que의 뒷자리로 넘어오게 된다.
현재 que에는 [getData(10) getData(40) getData(21)]이 들어있을 것이다.
그 다음 getData(10)이 stack으로 올라가고 await이 없으니 콘솔에 '1번 await 시켰습니다' 라고 출력한 뒤 그 뒤의 나머지 코드를 다 실행시킨 후에 종료된다. ( 더 이상 jebal2엔 await 존재 x )
현재 que에는 [getData(40) getData(21)]이 들어있을 것이다.
그 다음 getData(40)이 stack으로 올라가서 뒤에 1을 더한 후에 다시 한번 await을 만나 que의 뒷자리로 넘어오게 된다.
현재 que에는 [getData(21) getData(41)]이 들어있을 것이다.
그 다음 getData(21)이 stack으로 올라가고 더이상 await이 없으니 콘솔에 '2번 await 시켰습니다' 라고 출력한 뒤 그 뒤의 나머지 코드를 다 실행시킨다 ( jebal엔 await 가 하나 더 존재 무려 3겹 )
그리고 await3겹짜리를 만나서 다시 비동기로 getData(30)을 보낸다.
현재 que에는 [getData(41) getData(30)]이 들어있을 것이다.
이제 번갈아 가면서 주거니 받거니
que에는 30 42 , 42 31 , 43 32, 이렇게 돌아가면서 끝낸다.
여기서 알 수 있는 점💡
1. await에서 비동기함수를 처리할 때는 하나씩 que에 넣어가면서 기존 비동기 함수가 실행 되듯이 실행된다.
앞서 promise와 같음
(나는 await안에 있는 getData를 비동기를 동기로 처리 해주는 줄 알았다. )
2. 하지만 async함수 내에서 await이 끝나기 전까지는 그 다음 코드로 이동이 안된다. 그리고 await이 끝나면 그 다음 await이 없다면 다 실행 시킨 뒤 끝나지만 또 await이 있다면 그 await안의 코드를 비동기로 처리하고 끝날 때 까지 그 다음 코드로 이동 X
3. async 함수가 여러개 존재하고 그 안에서 await을 사용 했다면 각 async함수의 await들이 번갈아 가면서 비동기 처리가 된다. 1번내용과 같음
이 예제 위에서 아래와 같이 출력이 되었던 이유는
jebal 함수가 하나밖에 없었기 때문이었다.. 하나 밖에 없다면 await가 끝나기 전까지 비동기에서 계속 같은 getData가 돌기 때문
아래의 사이트에 내가 궁금했던 점들이 정말 잘 설명이 되어있다.
움짤로 보는 자바스크립트 동작 원리(5) - Promises & Async/Await (tistory.com)
'AI 웹개발 트랙 - 내배캠 > 9주차 ~' 카테고리의 다른 글
docker hub, pull, run (0) | 2022.03.07 |
---|---|
docker란? docker설치 (0) | 2022.03.07 |
팀과제 그림제작 페이지 (1) | 2022.03.03 |
async await (0) | 2022.02.28 |
promise 도대체 이게 뭐냐고..ㅜㅜㅜㅜ (0) | 2022.02.28 |