안녕하세요 YB 김고은입니다.
이번주 자스핑 주차인 비동기 처리에서 대해서 작성해보겠습니다!
공부한 책의 범위는 다음과 같습니다.
42장 (809 - 812) - Event Loop (비동기처리 챕터)
45장 (842 - 864) - Promise
45장 (842 - 848) - Callback
46장 (880- 883) - Async/Await
비동기 처리
자비스크립트엔진은 단 하나의 실행 컨텍스트 스택을 갖게 되는데,
-> 동시에 2개 이상의 함수를 동시 실행할 수 없다는 것이다.
이러한 작동의 방식을 싱글 스레드라고 하는데,
싱글 스레드의 특징상, 한번에 하나의 테스크만을 실행하기 전처리가 길어지면 이제 뒤에 실행될 동작이 막혀버리는
블로킹 (작업 중단) 이 발생한다.
.. 이러한 처리를 동기적인 처리 방식이라고 한다
(정리) => 자바스크립트는 하나의 실행 컨텍스트 스택만을 가지는, 싱글 스레드 언어이기 때문에 동기적 처리 방식으로 동작하며
이로 인해 블로킹이 발생한다.
sleep 함수에서 delay를 3초 걸어준 후 foo를 호출하고 나서, bar가 호출되는 코드가 있다고 했을때
코드의 실행 컨텍스트 스택은 아래와 같다.
이때 sleep 이 종료될때까지 다음 실행될 테스크(foo / bar)는 대기하기 때문에 실행 순서가 보장되지만,
블로킹된다는 단점이 있다.
반면, 콜백함수를 이용한 경우에는, 비동기 처리가 가능하다
(실행 중인 테스크가 종료되지 않은 상태라고 해서 곧바로 다음 테스크를 실행할 수 있다는 말)
-> 다만, 콜벡 지옥을 야기해, 가독성 bad && 에러 예외처리가 불편, 여러개의 비동기 처리를 한번에 하는 데에도 한계
아무튼 타이머 함수 (setTime, setInterval), Http 요청, 이벤트 핸들러에서 비동기 처리 방식으로 동작
이벤트 루프
이벤트 루프를 통해, 자바스크립트의 동시성을 지원 받음
자바스크립트 엔진 -> ( 콜 스택 // 힙 )
1️⃣ 콜스택 == 실행 컨텍스트 스택
소스코드 평가 과정에서 생성된 실행 컨텍스트 추가 && 제거되는 스택 구조
함수 호출시, 쌓여(push) -> 순차적으로 실행
이떄 싱글 스레드인만큼 콜스택도 하나라, 최상위 실행 컨텍스트 (지금 실행중인거)가 제거되기 전까지 다른 테스크는 실행될 수 없다.
2️⃣ 힙
객체가 저장되는 메모리 공간 (실행 컨텍스트가 이제 힙에 저장된 객체를 참조 )
원래) 메모리 공간 크기 결정 -> 메모리 값 저장
객체는 원시값과는 달리, 크기가 정해지지 않음 ❌ -> 할당할 메모리 공간 크기를 동적으로 결정
힙은 구조화되어 있지 않다는 특징이 있음!
자바스크립트 엔진은 이제 콜스택에서 테스크 넣고 순차적으로 빼면서 실행되는데,
이제 비동기처리에서 호출 스케줄링(콜백함수 등록 / 타이머 설정) 은 브라우저나 Node js가 담당한다.
비동기 처리를 위해서 태스크 큐와 이벤트 루프가 제공된다!
3️⃣ 테스크 큐
비동기 함수의 콜백 함수 또는, 이벤트 핸들러가 일시적으로 보관되는 영역 (like)
테스크 큐와는 별도의 존재로, 프로미스의 후속 처리 메서드의 콜백함수가 일시적으로 보관되는 마이크로테스크 큐도 존재
4️⃣ 이벤트 루프
콜 스택에, 현재 실행중인 컨텍스트가 있는지 && 테스크 큐에 대기 중인 함수(콜백함수, 이벤트 핸들러)가 있는지를 체크
if)
콜 스택 Empty && 테스크 큐에 대기중인 함수가 있다면 => 이벤트 루프는 순차적으로 테스크 큐에 대기중인 함수를 콜스택으로 이동시킴
태스크 큐에 잠시 보관된 콜백함수나 이벤트 핸들러를 이제 비동기 처리 하는것!!
Function foo() {
console.log('foo');
}
function bar() {
console.log('bar');
}
setTimeout(foo, 0); // 0초 (실제는 4ms) 후에, foo 함수 호출
bar( );
(왜: 4ms임? 지연시간이 0 이지만 -> 최소 지연시간은 4ms )
1. 전역 코드 평가 -> 콜스택.push(전역 실행 컨텍스트)
2. 전역 코드 실행 전 타이머 함수 호출 && 함수 실행 컨텍스트 생성 및 스택 푸시
콜스택.push(setTimeout 함수의 실행 컨텍스트)
3. 타이머 함수 실행 -> 콜백함수 함수 스케줄링(콜백함수를 테스크큐에 push ) && 콜스택 팝
4. 브라우저가 수행하는 4-1과, 자바스크립트 엔진이 수행하는 4-2는 병행처리
4-1. 브라우저는 타이머 설정 && 타이머 만료시, 테스크큐.push(콜백함수 foo)
_ 4ms 후에 테스크큐에 푸시되어서 대기하게 된다. 하지만, 호출의 경우에는 콜스택이 비어야지 될 수 있기 때문에 지연시간 후에 바로 호출된다는 보장은 없음 (시간차 발생 가능 )
4-2. 자바스크립트 엔진 bar함수 호출 && bar 함수의 실행 컨텍스트 생성 및 콜스택 푸시 && 함수 종료 후 팝
이때 브라우저가 타이머를 설정한 후 4ms가 지났다면, foo 함수는 아직도 테스크 큐에서 대기중
5. 전역 코드 실행이 종료 && 전역 실행 컨텍스트 팝
6. 이벤트 루프에 의해, 콜스택 비어있음이 감지되고 -> 테스크 큐에서 대기중이던 콜백함수 foo가 콜스택에 푸시된다.
여기에서 키는 🔑
setTimeout의콜백함수 (foo)는 태스크큐에 푸시되어 대기하다가 콜스택이 비게되면, (전역 코드 및 명시적으로 호출된 함수가 모두 종료시)
비로소 콜스택에 foo 함수 실행 컨텍스트가 푸시되어 실행
자바스크립트는 -> 싱글 스레드 방식으로 작동
브라우저는 -> 멀티 스레드로 작동
(그래서 비동기 처리가 가능한 것임!!)
프로미스
콜백 함수의 단점
프로미스의 경우, 콜백 지옥의 단점으로 인해 도입되었다고 한다.
이로 인해, 비동기 처리 중 에러 핸들링이 곤란하고, 여러개의 비동기 처리가 어렵다.

get('/step1', (a) => {
get(`/step2/${a}`,(b) => {
get(`/step3/${b}`,(c) => {
get(`/step4/${c}`,(d) => {
console.log(d);
});
});
});
});
또한, try catch 로 에러를 핸들링할 수 없다.
왜냐하면, setTimeout 의 함수의 콜백 함수가 실행될 시점에서, setTimeout 의 함수의 실행컨텍스트는 이미 스택에서 제거되었다.
이는, setTimeout 의 함수가, setTimeout 의 함수의 콜백함수를 호출한 것이 아니라는 뜻이다.
(호출자가 setTimeout 이려면, 콜백함수가 현재 실행중인 컨텍스트일때 setTimeout 이 하위 실행 컨텍스트여야 하는데 이게 아니라서 ,)
에러는 호출자 방향으로 전파되기 때문에(하위 실행 컨텍스트로),
setTimeout 함수의 콜백 함수가 발생시킨 에러는 catch 블 록에서 캐치되지 않는다
그래서 우리가 사용할 프로미스는!
프로미스
프로미스는 new 연산자로 호출되며, 반환값으로 프로미스 객체를 생성한다. (성공 실패에 대한 정보)
(프로미스는 ECMASCript 사양에 정의된 표준 빌트인객체란다! )
const promise = new Promise((resolve, reject) => {
if (/* 비동기 처리 성공 */) {
resolve('result');
} else {
reject('failure reason');
}
});
이때 프로미스는 비동기 처리에 대한 상태정보를 3가지 갖는다. (매우중요)
이때 pending 을 거쳐, settled 상태인 resolve 또는 reject 함수를 호출하는 것으로 프로미스의 상태는 귀결된다.
(정리) 프로미스는 비동기 처리 상태와 처리 결과를 관리하는 객체

프로미스 후속 처리 메서드
then catch finally 를 사용해서,
비동기 처리 상태가 변화하면, 처리 결과에 따라 후속 처리 메서드에 인수로 전달할 콜백함수가 선택적으로 호출된다!
(후속 처리 메서드의 콜백 함수에 <- 프로미스의 처리결과가 인수로 전달된다 )
1️⃣ then 메소드
~~ 한 후에 처리한다, 라고 생각하면 더 편할듯 하다.
성공의 경우 비동기 처리 결과를 인수로 받고 // 실패의 경우 프로미스의 에러를 인수로 전달받는다
2️⃣ catch 메소드
only 프로미스가 reject 인 상태일때만 호출된다. (then과 마찬가지로 프로미스를 반환)
3️⃣ finally 메소드
비동기 처리의 성공/실패 여부와 관계없이, 무조건 한번 호출되는 것이 특징.
(마찬가지로 프로미스 반환)
다음의 메소드를 이용해서, 콜백 함수 패턴보다는 가독성있게 코드 작성이 가능하고,
에러 핸들링도 가능하지만, 프로미스 체이닝이 발생할 수 있다는 단점이 있다.
(무한 then 지옥 )
프로미스의 정적 메서드
물론 new를 붙여 만드는 생성자 함수로 사용되지만, 함수도 객체이므로 메서드를 가질 수 있다고 한다.
1️⃣ Promise.resolve / Promise.reject
인수로 전달받은 값을 resolve/reject 하는 프로미스를 생성하는데,
이미 존재하는 값을 래핑해서 프로미스를 생성하기 위해 사용
2️⃣ Promise.all ⭐️
여러개의 비동기 처리를 병렬적으로 동작시키고 싶을때 사용한다.
인수로 배열을 받으며, 그 배열 내부에 비동기적으로 처리할 함수를 넣어주면 된다.
const requestData1 = () =>
new Promise((resolve) => setTimeout(() => resolve(1), 3000));
const requestData2 = () =>
new Promise((resolve) => setTimeout(() => resolve(2), 2000));
const requestData3 = () =>
new Promise((resolve) => setTimeout(() => resolve(3), 1000));
Promise.all([requestData1(), requestData2(), requestData3()])
.then(console.log) // [1, 2, 3] → 약 3초 소요 / 6초가 아님!!
.catch(console.error);
이때 비동기 처리들은 서로 의존적이지 않으며, 개별적으로 수행된다!
하지만, Promise.all 메서드는 인수로 전달받은 배열의 프로미스가 하나라도 rejected 되면
나머지 프로미스들의 상태 완료를 기다리지 않고 즉시 종료한다.
가장 먼저 reject 한 상태가 된 정보를 catch 의 인수로 전달한다.
3️⃣ Promise.race
Promise.all 과 유사하게 프로미스를 요소로 갖는 배열을 인수로 전달받지만,
모든 프로미스가 fulfilled 될때까지 기다리는 것이 아닌 가장 먼저 fulfilled 상태가 된 프로미스 처리 결과를 resolve 하는 새로운 프로미스를 반환한다. 다만, 하나라도 reject 될 경우 Promise.all 과 같이 즉시 종료
4️⃣ Promise.allSettled
프로미스를 요소로 갖는 배열을 인수로 전달받으며, 전달받은 프로미스가 모두 settled 상태가 될 경우 처리 결과를 반환한다
settled 상태란 성공과 실패 모두 포함한다.
프로미스의 처리 결과 객체는 다음과 같다.
[
{ status: 'fulfilled', value: 1 }, // 성공
{ status: 'rejected', reason: Error: Error! at <anonymous>:3:60 } // 실패
]
제너레이터 🆕
제너레이터 함수 호출시, 일반 함수처럼 코드 블록을 실행하는게 아니라 🙅🏻♀️
제너레이터 객체를 생성해 반환한다. 반환한 객체는 -> 이터러블이면서 이터레이터이다.
(이터러블: 반복 가능한 객체이며, Symbol.iterator라는 특별한 메서드를 가짐)
(이터레이터 : next() 메서드를 가진 객체) -> { value, done } 형태의 객체를 반환
제너레이터 객체는 next 메서드를 갖는 이터레이터인 동시에, 기존 이터레이터에 없는 return 과 throw 메서드를 갖는다.
이 세가지 메서드 호출시 다음과 같이 동작!
function* genFunc() {
try {
yield 1;
yield 2;
yield 3;
} catch (e) {
console.error(e);
}
}
const generator = genFunc();
console.log(generator.next()); // { value: 1, done: false }
console.log(generator.return('End')); // { value: "End", done: true }
console.log(generator.throw('Error!')); // {value: undefined, done: true}
1️⃣ next 메서드를 호출 ->
제너레이터 함수의 yield 표현식까지 코드 블록을 실행하고,
yield된 값을 value 프로퍼티 값으로, false 를 done 프로퍼티 값으로 갖는 이터레이터 결과 객체를 반환
2️⃣ return 메서드를 호출 ->
인수로 전달받은 값을 value 프로퍼티 값으로, true를 done 프로퍼트 값으로 갖는 이터레이터 결과 객체를 반환
3️⃣ throw 메서드를 호출 ->
인수로 전달받은 에러를 발생시키고, undefined를 value 프로퍼타 값으로, true를 done으로 갖는 이터레이터 결과 객체를 반환
제너레이터 함수의 비동기 처리
제너레이터 함수는 next 메서드와 yield를 통해 함수 호출자와 함수의 상태를 주고 받을 수 있다.
후속 처리 메서드 then/catch/finally 없이 비동기 처리 결괴를 반환하도록 구현이 가능한 것이 특징이다.
const fetch = require('node-fetch');
// 제너레이터 실행기
const async = (generatorFunc) => {
const generator = generatorFunc(); // 제너레이터 함수를 호출 -> 제너레이터 객체 생성
// 프로미스가 처리될 때마다 호출되는 함수
const onResolved = (arg) => {
const result = generator.next(arg); // 제너레이터에서 next() 호출, 인자로 전달된 arg는 첫 번째 yield 값
// 제너레이터가 종료된 경우
return result.done
? result.value // done이 true이면 제너레이터 실행이 끝난 것이므로, value 반환
: result.value.then((res) => onResolved(res)); // done이 false이면 then()을 사용하여 onResolved 호출
};
return onResolved();
};
async(function* fetchTodo() {
// 제너레이터 함수 내에서 비동기 작업을 수행
const url = 'https://jsonplaceholder.typicode.com/todos/1';
const response = yield fetch(url); // fetch는 프로미스를 반환하고, yield로 이를 대기함
const todo = yield response.json(); // response.json()은 또 다른 프로미스를 반환하고, yield로 이를 대기함
console.log(todo);
// 콘솔: { userId: 1, id: 1, title: 'delectus aut autem', completed: false }
})();
- async 함수 호출시 -> 제너레이터 함수 fetchTodo를 호출하고, 제너레이터 객체 생성
- 이후 onResolved 함수 반환에 따라 next 메서드 호출
- 첫 next 메서드 호출시, fetchTodo의 첫 번째 yield 문까지 실행되고, 그 결과의 반환값의 done 프로퍼티가 false (끝까지 실행되지 않으면)라면 삼항연산자에서 처럼, 재귀호출이 일어난다.
- 이제 res 객체를 할당하고, next 메서드를 두번쨰로 호출하면서 제너레이터함수 fetchTodo의 두번째yield 까지실행
- 마찬가지로 그 결과의 반환값의 done 프로퍼티가 false (끝까지 실행되지 않으면)라면 삼항연산자에서 처럼, 재귀호출 아니면, value가 전달된다.
- 이제 todo 객체를 next 메서드에 인수로 전달하면서 next를 세번째 호출하고, fetchTodo 가 끝까지 실행된다.
- 이때 next 메서드가 반환한 객체의 done 프로퍼티 값이 true 라면, value 프로퍼티 값을 그대로 반환하고 종료된다.
콜백함수
콜백함수의 경우 저번시간에 다룬 바 있고, 이벤트 루프때 충분히 예제와 함께 봤기 때문에
지난주 아티클을 첨부하겠다 🥸
https://wave-web.tistory.com/141
[week 3] 콜백함수 , this , 클로저
안녕하세요 YB 김고은입니다. 이번 주차는 콜백함수 (12강 _ 12.7.4) / this (342 ~ 354) / 클로저 (389 ~ 413)를 다뤄보고자 합니다.이번 주차도 마찬가지로 내용 정리를 중점으로, 약간의 호기심을 해결
wave-web.tistory.com
async/await
프로미스의 then/catch/Finally 후속처리 메서드에 콜백함수를 전달해서 비동기처리 결과를 후속처리 할 필요없이
마치 동기적 동작 처럼 프로미스를 사용할 수 있다는 장점!! (프로미스 체이닝 극복)
1️⃣ async 함수
async 함수는 언제나 피로미스를 반환, 또한, async 함수 내부에서만 await사용 가능
2️⃣ await 키워드
프로미스가 settle가 될때까지 대기 후 -> resolve 처리 결과를 반환!
(Promise.all 과 차이가 있음 주의 )
async function foo() {
const a = await new Promise((resolve) =>
setTimeout(() => resolve(1), 3000)
);
const b = await new Promise((resolve) =>
setTimeout(() => resolve(2), 2000)
);
const c = await new Promise((resolve) =>
setTimeout(() => resolve(3), 1000)
);
console.log([a, b, c]); // [1, 2, 3]
}
foo(); // 약 6초 소요
한편, 에러 처리와 관련해서는 try catch 를 이용해서 핸들링을 할 수 있다.
(콜백 함수와의 차이점! )
'자스핑' 카테고리의 다른 글
[week 04] 비동기 처리 약속해줘 🤙🏻 (Event Loop, Promise, Async/Await) (0) | 2024.11.21 |
---|---|
[week 04] 비동기 처리 (Event Loop, Promise, Callback, Async/Await) (0) | 2024.11.21 |
[week 03] this is me (0) | 2024.11.14 |
[week3] this is .. (0) | 2024.11.13 |
[week 3] 콜백함수 , this , 클로저 (0) | 2024.11.13 |