2023. 4. 3. 15:21ㆍ개발공부 기강잡자/React | JavaScript | NodeJS
<Udemy 리액트 챌린지 1주차>
이정환 강사님의 강의 '한입 크기로 잘라먹는 리액트'를 수강하고 있습니다.
Udemy에서 진행하고 있는 리액트 챌린지에 참여하며 1주차 미션으로 "섹션 3 JavaScript 응용"을 수강한 내용을 정리하고 있습니다. 👍
Spread 연산자
const cookie = {
base : "cookie",
madeIn : "Korea",
};
const chocochipCookie = {
base : "cookie",
madeIn : "Korea",
toping : "chocochip"
};
const blueberryCookie = {
base : "cookie",
madeIn : "Korea",
toping : "blueberry"
};
const strawberryCookie = {
base : "cookie",
madeIn : "Korea",
toping : "strawberry"
};
같은 프로퍼티를 갖는 여러 종류의 Cookie
객체를 생성할 때, 똑같은 코드가 반복된다.
(중복 프로퍼티 base, madeIn
를 가지고 있으며, toping
value만 다름)
👉 반복되는 코드 작성으로 생산성과 코드의 가독성이 나빠진다.
Spread 연산자 ...
를 사용하여 개선
const cookie = {
base : "cookie",
madeIn : "Korea",
};
const chocochipCookie = {
...cookie, // cookie의 속성 받아옴
toping : "chocochip"
};
const blueberryCookie = {
...cookie,
toping : "blueberry"
};
const strawberryCookie = {
...cookie,
toping : "strawberry"
};
...
가 Spread 연산자이다.
...cookie,
👉 모든 OOOCookie
객체가 공통적으로 갖는 cookie
의 프로퍼티 값을 받아온다
객체와 배열에 사용 가능
const noTopingCookies = ['촉촉한쿠키', '안촉촉한쿠키'];
const topingCookies = ['바나나쿠키', '블루베리쿠키', '딸기쿠키', '초코칩쿠키'];
// noTopingCookies + topingCookies
const allCookies = [...noTopingCookies, ...topingCookies];
동기 & 비동기
- 동기 : 순서대로 작동
- 비동기 : 순서대로 작동하지 않음
자바스크립트는 싱글스레드 방식으로 코드가 작성된 순서대로 작업을 처리한다.
이전 작업이 진행 중일 때는 이전 작업이 완료될 때까지 기다렸다가 다음 작업을 수행한다.
= 동기방식의 처리 (블로킹 방식)
function taskA(){
console.log("TASK A");
}
function taskB(){
console.log("TASK B");
}
function taskC(){
console.log("TASK C");
}
위와 같은 함수를 taskA() → taskB() → taskC() 순서로 호출했을 때, 아래와 같이 순서대로 함수가 수행된다.
동기처리 방식의 경우, 수행해야하는 작업들이 오래 걸리는 경우 다음 작업은 계속 기다려야하는 문제가 발생한다. 그렇게 되면 전체 작업이 끝나는 시간이 느려진다.
위 처럼 taskB 의 수행 시간이 길어지는 경우 taskC는 이전 작업이 끝날 때까지 기다려야한다.
스레드를 여러개 사용하여 작업을 각각 실행하도록 하면 MultiThread를 사용하면 작업의 분할이 가능해진다. 그러면 전체 Task를 끝내는 시간이 짧아진다. → 그러나 JavaScript는 싱글스레드 방식을 사용한다.
비동기 작업
: 동기 작업의 단점을 해결하기 위한 방법으로, 하나의 스레드에 여러 개의 작업을 동시에 실행시켜 먼저 작성된 코드의 결과를 기다리지 않고 다음 코드를 바로 실행하도록 한다.
→ 비동기 작업 (논블로킹 방식)
그런데 이렇게 동시에 여러 작업을 실행시킨다면, 한 작업이 정상적으로 끝났는지, 결과값은 어떻게 확인?
→ 각 함수가 완료되면 콜백함수를 수행하도록 콜백함수를 정의한다‼️
콜백함수
: 비동기함수의 수행여부, 결과를 확인할 수 있도록 정의함
각 task 가 끝나고 수행할 콜백함수를 정의하여 비동기함수의 수행여부와 그 결과를 알 수 있다.
만약 taskA
가 끝나면, A 콜백이 수행되어 taskA
함수의 결과값인 resultA
를 매개변수로 받아서 'A 끝났습니다. 작업결과 : {resultA}'
를 출력한다.
동기 방식
function taskA() {
console.log("A 작업 끝");
}
taskA();
console.log("코드 끝");
taskA를 동기방식으로 호출하면 순서대로 A 작업 끝 → 코드 끝
으로 출력된다.
비동기 방식
function taskA() {
setTimeout(() => {
console.log("A 코드 끝");
}, 2000); // 2초 뒤에 콜백함수 수행
}
taskA();
console.log("코드 끝");
taskA를 setTimeout
함수를 사용하여 2초 뒤에 콜백 함수를 호출한다.
taskA
호출 부의 다음 코드는 taskA()
가 끝나길 기다리지 않고 코드 끝
을 출력한다.
비동기함수의 수행이 끝난 뒤, setTimeout
의 콜백함수는 A 코드 끝
을 출력한다.
비동기함수의 결과값 이용
function taskA(a, b, cb) { // 지역변수를 사용할 수 있도록 콜백함수 cb 선언
setTimeout(() => {
// 3초뒤 수행되는 부분
const res = a + b;
cb(res); // 콜백함수 호출
}, 3000);// 3초 뒤에 setTimeout의 콜백함수 수행
}
taskA(3, 4, (res) => {
console.log("A result: ", res);
}); // 콜백함수가 taskA의 결과값을 받아서 출력
console.log("코드 끝");
- taskA의 콜백함수를 함수 내부에서 지역변수로 호출할 수 있도록 taskA의 매개변수로 cb 를 선언했다.
- setTimeout을 사용하여 3초 뒤에 setTimeout의 콜백함수가 res
를 구하고, taskA의 콜백함수 cb()
로 res
값을 넘겨주면 res 값을 받은 cb()
는 res
의 값을 출력한다.
- 출력순서 :
"코드 끝" → "A result: 7"
비동기함수 2개 사용
function taskA(a, b, cb) {
// 지역변수를 사용할 수 있도록 콜백함수 선언
setTimeout(() => {
const res = a + b;
cb(res); // 콜백함수 호출
}, 3000); // 3초 뒤에 콜백함수 수행
}
function taskB(a, cb) {
setTimeout(() => {
const res = a * 2;
cb(res); // 콜백함수 호출
}, 1000);
}
taskA(3, 4, (res) => {
console.log("A result: ", res);
}); // 콜백함수가 taskA의 결과값을 받아서 출력
taskB(7, (res) => {
console.log("B result: ", res);
});
console.log("코드 끝");
taskA와 taskB가 동시에 실행되지만 taskB()는 1초 뒤 콜백함수 cb(res)를 호출하고 taskA()는 3초 뒤 콜백함수 cb(res)를 호출하기 때문에 B의 결과가 A보다 먼저 출력된다.
- 출력순서 :
“코드 끝” → “B result : 14” → “A result : 7”
비동기 함수 3개 예제
function taskA(a, b, cb) {
// 지역변수를 사용할 수 있도록 콜백함수 선언
setTimeout(() => {
const res = a + b;
cb(res); // 콜백함수 호출
}, 3000); // 3초 뒤에 콜백함수 수행
}
function taskB(a, cb) {
setTimeout(() => {
const res = a * 2;
cb(res); // 콜백함수 호출
}, 1000);
}
function taskC(a, cb) {
setTimeout(() => {
const res = a * -1;
cb(res); // 콜백함수 호출
}, 2000);
}
taskA(3, 4, (res) => {
console.log("A result: ", res);
}); // 콜백함수가 taskA의 결과값을 받아서 출력
taskB(7, (res) => {
console.log("B result: ", res);
});
taskC(14, (res) => {
console.log("C result: ", res);
});
console.log("코드 끝");
- taskB() : 1초 뒤 수행
- taskC() : 2초 뒤 수행
- taskA() : 3초 뒤 수행 되므로 출력 결과는 다음과 같다.
JS Engine의 작동방식
- Heap과 Call Stack 영역으로 나뉘어져 있다.
- Heap : 변수나 상수 등 메모리 할당에 사용되는 영역
- Call Stack : 코드의 실행에 따라서 스택을 따름
CallStack
- 동기방식으로 호출하는 경우
function one(){
return 1;
}
function two(){
return one() + 1;
}
function three(){
return two() + 1;
}
console.log(three());
- 코드 실행 : 자바스크립트 코드가 실행되면 최상위 Context 인
Main Context
가 CallStack에 들어옴 - 함수 호출
: 호출된 함수three()
가 Call Stack에 추가 - 함수 수행
:three()
함수 내에서 호출된 함수two()
가 Call Stack에 추가 - 함수 수행
:two()
함수 내에서 호출된 함수one()
이 Call Stack에 추가 one()
함수 수행one()
함수 수행 완료 : Call Stack에서one()
제거
→ 종료된 함수는 바로 Call Stack에서 제거 (Stack - LIFO)two()
함수 수행 완료
: Call Stack에서two()
제거three()
함수 수행 완료
: Call Stack에서three()
제거- 코드 종료
: Call Stack에서Main Context
제거
⇒ JavaScript는 Call Stack이 하나이므로 싱글 스레드로 동작한다!
CallStack
- 비동기방식으로 호출하는 경우
function asyncAdd(a, b, cb){
setTimeout(() => {
const res = a + b;
cb(res);
}, 3000);
}
asyncAdd(1, 3, (res) => {
console.log("결과 : ", res);
});
비동기처리를 위해서 WebAPIs, Callback Queue, Event loop 가 추가가 됨
- 코드 실행 :
Main Context
가 CallStack에 들어옴 asyncAdd()
호출 : CallStack에asyncAdd()
추가- 비동기 함수
setTimeout()
호출 : CallStack에setTimeout()
와 콜백함수cb()
추가
→ 비동기함수이므로 WebAPIs 로 전달
WebAPIs에서 3초를 기다림 asyncAdd()
함수 수행완료
: CallStack에서 제거- 3초 지난 뒤 WebAPIs의 cb() 함수가 Callback Queue로 옮겨짐
→ CallStack에Main Context
이외에 다른 함수가 남아있지 않으면cb()
은 Event Loop에 의해 CallStack으로 이동됨 cb()
수행cb()
수행 완료
: CallStack에서 제거- 코드 종료
: Call Stack에서Main Context
제거
👉 엔진의 수행방식을 알면 동기코드와 비동기코드를 동시에 사용할 때 문제가 발생한 경우 해결 방안을 찾아내기 수월해진다.‼️
비동기 함수 3개를 순서대로 호출하는 법
function taskA(a, b, cb) {
// 지역변수를 사용할 수 있도록 콜백함수 선언
setTimeout(() => {
const res = a + b;
cb(res); // 콜백함수 호출
}, 3000); // 3초 뒤에 콜백함수 수행
}
function taskB(a, cb) {
setTimeout(() => {
const res = a * 2;
cb(res); // 콜백함수 호출
}, 1000);
}
function taskC(a, cb) {
setTimeout(() => {
const res = a * -1;
cb(res); // 콜백함수 호출
}, 2000);
}
taskA(4, 5, (a_res) => {
console.log("A result: ", a_res);
taskB(a_res, (b_res) => {
console.log("B result: ", b_res);
taskC(b_res, (c_res) => {
console.log("C result: ", c_res);
});
});
});
console.log("코드 끝");
👆 비동기 함수를 이렇게 호출하면 코드끝-A-B-C 순서로 수행할 수 있다.
→ 비동기 처리의 결과를 다음으로 호출할 비동기함수의 인자값으로 전달
수행결과
하지만‼️‼️‼️ 함수 호출 부의 가독성이 너무 안 좋다. 이처럼 콜백이 계속 깊어지는 현상을 콜백 지옥이라고 한다.
→ Promise 객체를 통해 해결할 수 있다.
Promise
→ 콜백지옥을 해결하기 위한 방법!
taskA(4, 5, (a_res) => {
console.log("A result: ", a_res);
taskB(a_res, (b_res) => {
console.log("B result: ", b_res);
taskC(b_res, (c_res) => {
console.log("C result: ", c_res);
});
});
});
비동기 작업이 가질 수 있는 3가지 상태
- Pending (대기 상태) : 비동기 작업이 진행 중이거나, 실행될 수 없는 상태
- Fulfilled (성공) : 비동기 작업이 의도한대로 수행된 상태
- Rejected (실패) : 비동기 작업이 모종의 이유로 실패한 상태
(ex : 서버가 응답을 하지 않는 경우, 시간이 너무 오래걸려서 자동으로 취소가 된 경우 등)
→ 비동기 작업은 한번 성공/실패 시 재시도 없이 그냥 그걸로 작업이 끝난다.
상태 전이
- Pending → Fulfilled 로 변화 : Resolve (해결)
- Pending → Rejected 로 변화 : Reject (거부)
예제
isPositive()
: 2초 뒤에 매개변수number
가 숫자면 양수인지 음수인지 판별해서 resolve 콜백함수로 전달하고,
숫자가 아니면 reject 콜백함수를 호출하는 함수
function isPositive(number, resolve, reject) {
setTimeout(() => {
if (typeof number === "number") {
// 성공 -> Resolve 콜백
resolve(number >= 0 ? "양수" : "음수");
} else {
// 실패 -> Reject 콜백
reject("숫자 아님");
}
}, 2000);
}
// 성공시키기
isPositive(
10,
(res) => {
console.log("성공, 결과 : ", res);
},
(err) => {
console.log("실패, 원인 : ", err);
}
);
// 실패시키기
isPositive(
[],
(res) => {
console.log("성공, 결과 : ", res);
},
(err) => {
console.log("실패, 원인 : ", err);
}
);
👉 number가 숫자인 경우 resolve(인자값)
콜백함수를 호출하고, 숫자가 아닌 경우 reject(인자값)
콜백함수로 인자값을 전달한다.isPositive
함수를 호출할 때, resolve와 reject 콜백함수를 정의하여 콜백을 처리한다.
Promise 사용
function isPositiveP(number) {
const executor = (resolve, reject)=>{ // 실행자 : 비동기작업을 실질적으로 수행해주는 함수
setTimeout(() => {
if (typeof number === "number") {
// 성공 -> Resolve 콜백
resolve(number >= 0 ? "양수" : "음수");
} else {
// 실패 -> Reject 콜백
reject("숫자 아님");
}
}, 2000)
}
const asyncTask = new Promise(executor);
return asyncTask;
}
👉 비동기 작업을 실질적으로 실행하는 실행자인 executor
를 선언하고,
비동기 작업을 실행하기 위해서 asyncTask
라는 변수에 비동기 작업 자체를 저장한다.Promise
객체는 비동기 작업을 실질적으로 실행하는 함수 executor
를 인자로 전달받는다.
전달하는 순간 executor
함수가 수행된다.
→ isPositiveP 함수의 반환 Type이 Promise 인 것을 확인할 수 있음
💡어떤 함수의 Return Type이 Promise 면 비동기작업을 하는 함수라는 것을 알 수가 있다!
Return 된 Promise 객체 사용하기
형식 : res.then(()=>{성공한 경우 콜백함수}).catch(()=>{실패한 경우 콜백함수})
✔️ 비동기함수인 Promise 객체의 작업이 성공한 경우 then의 콜백함수로 처리가 되고, 실패한 경우 catch 의 콜백함수를 처리한다.
const res = isPositiveP(101);
res.then((res) => {
console.log("작업성공 : ", res);
})
.catch((err) => {
console.log("작업실패 : ", err);
});
출력 결과 : “작업성공 : 양수”
콜백지옥 탈출하기
→ 비동기함수의 결과를 다음 비동기함수의 인자값으로 넘겨야하는게 반복될 경우 발생하는 상황
taskA(4, 5, (a_res) => {
console.log("A result: ", a_res);
taskB(a_res, (b_res) => {
console.log("B result: ", b_res);
taskC(b_res, (c_res) => {
console.log("C result: ", c_res);
});
});
});
👉 Promise 객체로 바꿔서 위와 같은 콜백지옥을 해결해보자!
function taskA(a, b) {
const executerA = (resolve, reject) => {
setTimeout(() => {
const res = a + b;
resolve(res);
}, 3000); // 3초 뒤에 수행
};
return new Promise(executerA);
}
function taskB(a) {
return new Promise((resolve, reject) => {
setTimeout(() => {
const res = a * 2;
resolve(res);
}, 1000);
});
}
function taskC(a) {
return new Promise((resolve, reject) => {
setTimeout(() => {
const res = a * -1;
resolve(res);
}, 2000);
});
}
taskA(5, 1).then((a_res) => {
console.log("A result: ", a_res);
taskB(a_res).then((b_res) => {
console.log("B result: ", b_res);
taskC(b_res).then((c_res) => {
console.log("C result: ", c_res);
});
});
});
console.log("코드 끝");
Promise를 사용했지만 콜백 지옥 해결 안됨!
→ Promise 객체의 then 함수는 이렇게 사용하는게 아니다!!!⭐️
Then Chaining
taskA(5, 1)
.then((a_res) => {
console.log("A result: ", a_res);
return taskB(a_res); // taskB를 호출하고 그 결과값을 반환
})
.then((b_res) => {
console.log("B result: ", b_res);
return taskC(b_res);
})
.then((c_res) => {
console.log("C result: ", c_res);
});
하나하나 차근차근히 보면
taskA(5, 1)
→ 이부분의 반환값은 taskA()
함수에서 반환하는 Promise 객체
taskA(5, 1)
.then((a_res) => {
console.log("A result: ", a_res);
return taskB(a_res); // taskB를 호출하고 그 결과값을 반환
})
→ taskA()
함수에서 반환하는 Promise 객체의 then
메소드의 resolve
콜백함수는 "A result: 6"
를 출력하고, taskB(a_res)
를 리턴하도록 함 (taskB를 호출)
→ 그러면 이부분의 반환값은 taskB()의 리턴인 Promise객체
.then((b_res) => {
console.log("B result: ", b_res);
return taskC(b_res);
})
→ taskA()는 taskB()를 리턴했으므로 이 부분은 taskB()
의 리턴인 Promise
객체의 then
함수
→ resolve
콜백함수에서 "B result: 12"
를 출력하고, b_res
값을 taskC()
의 인자로 전달
.then((c_res) => {
console.log("C result: ", c_res);
});
→ taskB()는 taskC()를 리턴했으므로 이 부분은 taskC()
의 리턴인 Promise
객체resolve
콜백함수는 "C result: -12"
을 출력하게 된다.
❤️🔥 이렇게 하면 콜백지옥이 해결된다! = then Chaining
// taskA 호출
const aPromise = taskA(5, 1).then((a_res) => {
console.log("A result: ", a_res);
return taskB(a_res); // taskB를 호출하고 그 결과값을 반환
});
console.log("----");
// 콜백함수 부분 분리 가능
aPromise
.then((b_res) => {
console.log("B result: ", b_res);
return taskC(b_res);
})
.then((c_res) => {
console.log("C result: ", c_res);
});
→ 이런식으로 taskA 가 끝나고 수행되는 콜백함수 부분을 분리할 수도 있다.
async와 await
Async
: 비동기기술, Promise를 더욱 쉽게 다룰 수 있도록 해줌
async function helloAsync(){
return 'hello Async';
}
→ function
키워드 앞에 async
를 작성하여 함수를 선언하면 Promise객체를 반환하는 비동기함수를 선언할 수 있음
console.log(helloAsync()); // Promise 객체를 출력
따라서 'hello Async'
라는 문자열을 반환하는 helloAsync()
함수를 출력하면 문자열 'hello Async'
가 아니라 Promise 객체를 출력함
→ Promise 객체가 아닌 async 함수의 리턴값을 출력하기 위해선 then을 사용하여 핸들링해야한다!
helloAsync().then((res) => {
console.log(res);
});
async 함수의 Return값은 비동기함수의 반환값인 Promise의 resolve 결과값이 된다.
→ 따라서 then()
으로 핸들링할 수 있다.
Await
- 예제) 3초 기다렸다가 "hello Async"를 리턴하는 함수
delay(ms)
:setTimeout()
함수를 사용하여 3초의 delay를 발생시키는 함수를 정의helloAsync()
:delay
함수의 리턴인 Promise 객체가 resolve 된 경우 문자열 "hello Async"을 return
// ms만큼 기다렸다가 끝내는 함수 delay(ms)
function delay(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms); // ms 만큼 기다렸다가 별도의 처리 없이 resolve를 호출
});
}
async function helloAsync() {
// 3초 뒤 "hello Async"를 리턴
return delay(3000).then(() => {
return "hello Async";
});
}
⇒ 다소 소스가 복잡하고 길다..! await
키워드를 사용하여 Promise.then
을 사용하는 것보다 세련되게 소스 개선할 수 있다.! 👇👇👇👇
async function helloAsync() {
// 3초 뒤 "hello Async"를 리턴
await delay(3000); // 동기적으로 호출됨
return "hello Async";
}
await
키워드를 붙이고 비동기 함수를 호출하면 동기함수처럼 동작할 수 있다.
→ await
키워드 뒤에 있는 함수의 동작이 끝나기 전에는 그 아래의 코드를 실행하지 않는다. (동기적으로 수행)
⭐️ async 키워드가 붙은 함수 내에서만 사용할 수 있다.
함수 내에서 helloAsync()
도 await
을 사용해 호출해보기
async function main() {
const res = await helloAsync();
console.log(res);
}
main();
→ await
으로 호출했기 때문에 helloAsync()
는 동기적으로 호출되어 res
변수에는 "hello Async"
이 담긴다. await
으로 비동기함수가 호출되면 Promise가 처리될 때까지 기다린 후 결과값을 반환한다.
API & fetch
: API 호출! → 클라이언트가 서버에게 원하는 처리, 데이터를 요청하고 응답을 받을 수 있도록 하는 인터페이스 = API
Request ↔ Response
- ‼️ 서로 다른 시스템간의 Communication에서 서버의 응답을 언제 받을 수 있을지 모른다! ‼️
→ 동기적으로 처리할 수 없다..비동기 호출을 하자!
API 호출하기
- https://jsonplaceholder.typicode.com/ : 테스트용 더미데이터 JSON을 쓸 수 있는 곳
fetch()
: API 함수를 호출할 수 있도록 해주는 내장함수 (return Type : Promise > 비동기 호출)
then()
으로 fetch Response 값을 받기
let response = fetch("https://jsonplaceholder.typicode.com/posts").then(
(res) => {
console.log(res);
}
);
👉 fetch의 결과값 Response를 then을 통해 핸들링한다.
Response
객체: 결과 값을 담는 편지 봉투와 같은 것 ~
json()
메소드에 우리가 원하는 응답 값인 JSON 데이터가 담겨있다.JSON 데이터 확인하기
: Response로 받은 JSON을 확인하려면 Response 객체의 json()
메소드를 호출하여 가져올 수 있다.
async function getData() {
let rawResponse = await fetch("https://jsonplaceholder.typicode.com/posts");
let jsonResponse = await rawResponse.json();
console.log(jsonResponse);
}
getData();
출력결과
👉 100개의 JSON 객체가 담겨있는 결과값을 확인할 수 있다.
휴 드디어 1주차 미션인 섹션 2, 3을 모두 포스팅 했다. 이렇게 시간이 많이 걸려도 되나.
하나에 너무 많은 정성을 쏟다보면 금방 지칠 수도 있을 것 같다. 적정선을 잘 파악해서 적당한 시간이 소요될 수 있도록 해야겠다.
아무튼 자바스크립트를 쭉 복습할 수 있는 시간이었고, 이제 이 내용들을 바탕으로 리액트를 배우면 될 것 같다. 두근 두근 ❤️🔥
🌱 블로그도 열심히 꾸려나가기 🌱
출처 : Udemy '한입 크기로 잘라먹는 리액트' (이정환)
'개발공부 기강잡자 > React | JavaScript | NodeJS' 카테고리의 다른 글
[React] React 입문 (ReactApp 생성/JSX/ESM/State/Props) (1) | 2023.04.07 |
---|---|
[React] Node.js 기초 (모듈만들기/NPM/randomcolor 예제) (0) | 2023.04.04 |
[React] JavaScript 응용_1 (Truthy&Falsy/삼항연산자/단락회로평가/조건문업그레이드/비구조화할당) (0) | 2023.04.02 |
[React] JavaScript 기초_2 (객체/배열/반복문/배열내장함수) (0) | 2023.04.02 |
[React] JavaScript 기초 (변수/상수/조건문/함수/콜백함수) (1) | 2023.03.25 |