2. 자바스크립트 동시성과 비동기
2-1. 자바스크립트 동시성
- 자바스크립트와 쓰레드 :
자바스크립트는 싱글 쓰레드로 동작하는 언어입니다. (메인 쓰레드 하나와 콜스택 하나로 구성되어 있어요!)
그리고 비동기 작업을 동시에 할 수 있어요. 오잉? 🤢
1번에 1개의 작업만 할 수 있는데, 어떻게 동시 실행을 할까요?
→ 자바스크립트는 코어 엔진만 가지고 돌아가지 않아요! 실행환경(런타임)의 도움을 받아 동시 실행을 합니다.
( WebAPI(dom, ajax, setTimeout...), Event Queue, Event Loop 등과 함께 동작합니다.)
1) 쓰레드가 하나일 때 일어나는 일
아래 코드를 써보면 그 아래에 어떤 코드를 넣어두었더래도 멈춰있습니다.
일꾼이 하나니까 alert을 띄워주는 동안 다른 일을 못하는거예요.
+) 깊게 들어가면 이건 자바스크립트의 특징 중 하나인 run-to-completion이라는 것 때문인데요.
일 하나 끝내기 전엔 다른 건 하지 않는 거예요.
window.alert();
console.log("hi!");
2) 그런데 어떻게 비동기 작업을 하나?
비밀은 이벤트 루프! 😊
프론트엔드에서 자바스크립트는 혼자 독립 실행되는 게 아니고 브라우저를 통해 실행됩니다.
브라우저에서 자바스크립트를 실행할 때 이벤트 루프라는 걸 기반해서 실행하는데요,
이 이벤트 루프의 동작에 대해 알아봅시다.
- V8 엔진에서의 비동기 작업 처리
동시성(Concurrency)
자바스크립트는 기본적으로 한번에 하나의 일만 처리하지만,
우리가 만든 프로젝트가 돌아가는 걸 보면 여러 작업이 한 번에 처리되는 것처럼 느껴지죠!
이걸 두고 동시성이라고 부릅니다.
한 방에 여러개가 처리되는 것처럼 보이는 거요!
(실제로 동시에 실행되는 건 아니예요. 실제로 동시에 실행되는 건 병렬이라고 불러요. 🙂 )
- 용어정리
- heap : 동적으로 생성된 객체 인스턴스가 할당되는 영역
- call stack : 일거리가 쌓이는 스택
- event queue :
테스크 큐(Task queue)나 콜백 큐(callback queue)라고도 함.
비동기 처리 함수의 콜백 함수, 비동기식 이벤트 핸들러, 타이머의 콜백 함수를 넣어두는 큐
- event loop :
테스크(일거리)가 들어오길 기다렸다가 테스크가 들어오면 일을 하고,
일이 없으면 잠깐 쉬기를 반복하는 자바스크립트 내의 루프
→ call stack 내에서 현재 실행중인 일거리가 있는 지, 이벤트 큐에 일거리가 있는 지 반복해서 확인하고,
콜 스택이 비어 있으면 이벤트 큐의 일거리를 콜스택으로 옮겨가게끔 도와요.
- Web API : Ajax, DOM event, setTimeout 등 브라우저에 내장된 API
2-2. Promise
- 동기와 비동기
1) 동기:
하나 하나씩 처리되는 방식을 동기라고 해요.
뭘 해줘!하고 요청을 보내고 다했어! 라는 응답을 받은 후에 다음 일을 시작하는 방식입니다.
2) 비동기:
어떤 작업을 해!하고 요청을 보낸 다음,
그 작업을 다했다는 응답을 받을 때까지 기다리지 않고 바로 다음 일을 시작하는 방식입니다.
서버 등에서 데이터를 받아오는 등 시간이 좀 필요한 작업을 해야한다면, 응답을 받기까지 계속 기다릴 수 없잖아요. 🥺
그럴 때 비동기 처리를 합니다.
- 콜백이란? :
callback은 특정 함수에 매개변수로 전달된 함수예요.
A()가 B()를 콜백으로 받았다면 A()안에서 B를 실행할 수 있을거예요.
콜백 패턴은 자바스크립트가 비동기 처리를 하기 위한 패턴 중 하나입니다!
전통적인 콜백 패턴은 일명 콜백 헬로 불리는 엄청난 중첩 문제가 생기기 쉽습니다.
- 콜백 헬 :
꼬리에 꼬리를 무는 비동기 처리가 늘어나면 호출이 계속 중첩되고, 코드가 깊어지고, 관리는 어려워짐.
이런 깊은 중첩을 콜백 헬이나 멸망의 피라미드라고 부름.
function async1('a', function (err, async2){
if(err){
errHandler(err);
} else {
...
async2('b', function (err, async3){
...
}){
...
}
}
});
- 이런 콜백 헬이 발생하는 이유?
비동기 처리 시에는 실행 완료를 기다리지 않고 바로 다음 작업을 실행함.
즉, 순서대로 코드를 쭉 적는다고 우리가 원하는 순서로 작업이 이뤄지지 않음.
비동기 처리 함수 내에서 처리 결과를 반환하는 걸로는 원하는 동작을 하지 않으니,
콜백 함수를 사용해 원하는 동작을 하게 하려고 콜백 함수를 씁니다.
이 콜백 함수 내에서 또 다른 비동기 작업이 필요할 경우 위와 같은 중첩이 생기면서 콜백 헬이 탄생함. 😢
- 프라미스란? :
비동기 연산이 종료된 이후 결과를 알기 위해 사용하는 객체입니다!
프라미스를 쓰면 비동기 메서드를 마치 동기 메서드처럼 값을 반환할 수 있어요.
전통적인 콜백 패턴으로 인한 콜백 헬 때문에 ES6에서 도입한 또다른 비동기 처리 패턴입니다.
비동기 처리 시점을 좀 더 명확하게 표현할 수 있어요!
JavaScript 특성인 이벤트 루프의 규범을 따르지 않고 비동기 이벤트로 분류됩니다.
이벤트 루프는 Promise를 발견하면 비동기로 동작하는 기능(메서드, 함수)들이 이벤트 루프에서 분리됩니다.
비동기로 동작하는 기능들은 Promise가 이행(fulfilled)될 때까지 대기열(Queue)에 배치됩니다.
1) 프라미스 생성
프라미스는 Promise 생성자 함수(new 키워드)를 통해 생성.
비동기 작업을 수행할 콜백 함수를 인자로 전달받아서 사용.
// 프라미스 객체를 만듭니다.
// 인자로는 (resolve, reject) => {} 이런 excutor 실행자(혹은 실행 함수라고 불러요.)를 받아요.
// 이 실행자는 비동기 작업이 끝나면 바로 두 가지 콜백 중 하나를 실행합니다.
// resolve: 작업이 성공한 경우 호출할 콜백
// reject: 작업이 실패한 경우 호출할 콜백
const promise = new Promise((resolve, reject) => {
if(...){
...
resolve("성공!");
} else {
...
reject("실패!");
}
});
2) 프라미스의 상태값
- pending : 비동기 처리 수행 전(resolve, reject가 아직 호출되지 않음)
- fulfilled : 수행 성공(resolve가 호출된 상태)
- rejected : 수행 실패(reject가 호출된 상태)
- settled : 성공 or 실패(resolve나 reject가 호출된 상태)
3) 프라미스 후속 처리 메서드
- 프라미스로 구현된 비동기 함수는 프라미스 객체를 반환!
- 프라미스로 구현된 비동기 함수를 호출하는 측에서는
이 프라미스 객체의 후속 처리 메서드를 통해 비동기 처리 결과(성공 결과나 에러메시지)를 받아서 처리해야 함.
- .then(성공 시, 실패 시)
then의 첫 인자는 성공 시 실행, 두번째 인자는 실패 시 실행됨. (첫 번째 인자만 넘겨도 됨!)
// 프라미스를 하나 만들어 봅시다!
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("완료!"), 1000);
});
// resolve
promise.then(result => {
console.log(result);
// 완료!가 콘솔에 찍힐거예요.
}, error => {
console.log(error);
// 실행되지 않습니다.
});
// 프라미스를 하나 만들어 봅시다!
let promise = new Promise((resolve, reject) => {
setTimeout(() => reject(new Error("오류!")), 1000);
// Error를 강제로 던짐.
});
// reject
promise.then(result => {
console.log(result);
// 실행되지 않습니다.
}, error => {
console.log(error);
// Error: 오류!가 찍힐거예요.
});
- .catch(실패 시)
// 프라미스를 하나 만들어 봅시다!
let promise = new Promise((resolve, reject) => {
setTimeout(() => reject(new Error("오류!")), 1000);
});
promise.catch((error) => {console.log(error};);
- promise chaining(프라미스 체이닝)
1) 프라미스는 후속 처리 메서드를 체이닝해서 여러 개의 프라미스를 연결할 수 있음! (이걸로 콜백 헬을 해결할 수 있음!)
- 체이닝이 뭔데? 그걸 어떻게 하는 건데? :
후속 처리 메서드 (then)을 쭉쭉 이어 주는 것.
new Promise((resolve, reject) => {
setTimeout(() => resolve("promise 1"), 1000);
}).then((result) => { // 후속 처리 메서드 하나를 쓰고,
console.log(result);
// promise 1
return new Promise(...);
}).then((result) => { // 이렇게 연달아 then을 써서 이어주는 거예요.
console.log(result);
return new Promise(...);
}).then(...);
2-3. async, await
- async, await :
앞으로 정말정말 많이 보고 쓸 문법입니다. 프라미스 사용을 엄청 편하게 만들어줘요! 🙂
[더 알면 좋은 내용]
강의에서 다루지 않지만 제네레이터라는 함수와 이터러블을 알면 더더 좋습니다.
1) async :
- 함수 앞에 async를 붙여서 사용.
- 항상 프라미스를 반환함. (프라미스가 아닌 값이라도, 프라미스로 감싸서 반환해줌!)
// async는 function 앞에 써줍니다.
async function myFunc() {
return "프라미스를 반환해요!";
// 프라미스가 아닌 걸 반환해볼게요!
}
myFunc().then(result => {console.log(result)}); // 콘솔로 확인해봅시다!
2) await :
- async의 짝꿍. (async 없이는 못씀!)
- async 함수 안에서만 동작.
- await는 프라미스가 처리될 때까지 기다렸다가 그 이후에 결과를 반환!
async function myFunc(){
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("완료!"), 1000);
});
console.log(promise);
let result = await promise;
// 여기서 기다리자!하고 신호를 줍니다.
console.log(promise);
console.log(result);
// then(후처리 함수)를 쓰지 않았는데도, 1초 후에 완료!가 콘솔에 찍힐거예요.
}
await를 만나면, 실행이 잠시 중단되었다가 프라미스 처리 후에 실행을 재개합니다!
즉, await를 쓰면 함수 실행을 기다리게 하는거예요.
onClick = async() => {
try {
console.time("ABC");
const data001 = await new Promise((resolve) =>
setTimeout(resolve, 3000, "promise001")
);
const data002 = await new Promise((resolve) =>
setTimeout(resolve, 5000, "promise002")
);
console.timeEnd("ABC");
} catch(error) {
console.error(error);
}
};
표준 async/await 구문을 사용하여 두 개의 Promise를 처리하면,
코드가 순차적으로 처리되므로 첫 번째 Promise가 해결될 때까지 두 번째 Promise는 실행되지 않음.
첫 번째 Promise는 3초, 두 번째 Promise는 5초 걸릴 것으로 예상됨.
console.time()을 사용하여 onClick() 함수의 실행시간을 측정하면 대략 8초.
'(심층)리액트' 카테고리의 다른 글
다시 시작하는 리액트 - 리액트 심화 3-1 (0) | 2023.05.04 |
---|---|
다시 시작하는 리액트 - 리액트 심화 2-3 (0) | 2023.05.03 |
다시 시작하는 리액트 - 리액트 심화 2 Quiz (0) | 2023.05.02 |
다시 시작하는 리액트 - 리액트 심화 2-1 (0) | 2023.05.01 |
다시 시작하는 리액트 - 리액트 심화 1 과제 (0) | 2023.04.30 |
github : https://github.com/dnjfht
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!