- 동기 & 비동기
- 동기
- Javascript는 코드가 작성된 순서대로 작업을 처리한다.
- 이전 작업이 진행 중 일 때는 다음 작업을 수행하지 않고 기다린다.
- 먼저 작성된 코드를 먼저 다 실행하고 나서 뒤에 작성된 코드를 실행한다.
- 비동기
- 싱글 쓰레드 방식을 이용하면서, 동기적 작업의 단점을 극복하기 위해 여러 개의 작업을 동시에 실행 시킨다.
- 즉, 먼저 작성된 코드의 결과를 기다리지 않고, 다음 코드를 바로 실행한다.
function taskA(a,b, cb) {
// console.log("A 작업 끝");
setTimeout(() => {
const res = a + b;
cb(res);
}, 3000);
}
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 TASK RESULT : ", res);
});
taskB(7, (res) => {
console.log("B TASK RESULT : ", res)
});
taskC(10, (res) => {
console.log("C TASK RESULT : ", res)
});
// 비동기 방식 하나 더
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("코드 끝");
- 위처럼 task A,B,C를 setTimeout를 통해 비동기 방식으로 딜레이 시간을 부여하게 되면, 1. 동기 방식이라면 taskC가 실행되기 위해선 3초 + (A 메서드 실행시간) + 1초 (B 메서드 실행시간) + 2초 + (C 메서드 실행시간)만큼의 시간을 기다려야 TaskC의 결과를 확인 할 수 있다.
- 하지만 위의 경우 taskB(), taskC()는 각각 1,2 초의 딜레이가 있고 task()A의 경우 딜레이가 3초이기 때문에 taskA()가 완료될 때까지 기다리는 것은 효율적이지 못하다.
- 즉, 이를 해결하기 위해, 비동기 방식으로 동작하는 Javascript는 실행 순서에 상관없이 메서드를 실행하게 된다.
taskA(1,2,(a_res)=>{
console.log("REULST : ", a_res)
taskB(a_res,(b_res)=>{
console.log("RESULT : ", b_res)
taskC(b_res,(c_res)=>{
console.log("RESULT : ", c_res)
})
})
})
- 콜백 지옥
1. 위 코드와 같이 함수의 매개 변수로 넘겨지는 콜백 함수가 반복되어 코드의 들여쓰기 수준이 감당하기 힘들 정도로 깊어지는 현상을 말한다.
- 위의 콜백 지옥은 Javascript의 Promise를 통해 해결 할 수 있다.
Promise
- 프로미스는 자바스크립트 비동기 처리에 사용되는 객체이다. 자바스크립트의 비동기 처리란 '특정 코드의 실행이 완료될 때까지 기다리지 않고 다음 코드를 먼저 수행하는 자바스크립트의 특성'을 의미한다.
- Promise의 3가지 상태 (states)
1. Pending(대기) : 비동기 처리 로직이 아직 완료되지 않은 상태
2. Fullfilled(이행) : 비동기 처리가 완료되어 프로미스가 결과 값을 반환해준 상태
3. Rejected(실패) : 비동기 처리가 실패하거나 오류가 발생한상태
- Pending(대기)
1. new Promise() 메서드를 호출하면 대기(Pending) 상태가 된다.
2. new Promise() 메서드를 호출할 때 콜백 함수를 선언할 수 있고, 콜백 함수의 인자는 resolve, reject이다.
- Fuilfilled(이행)
1. 콜백 함수의 인자 resolve를 실행하면 Fulfilled(이행) 상태가 된다.
2. 이행 상태가 되면 then()를 이용하여 해당 메서드의 처리 결과 값을 받을 수 있다.
- Rejected(실패)
1. new Promise()로 Promise 객체를 생성하면 콜백 함수 인자로 resolve와 reject를 사용할 수 있다
2. reject를 호출하면 실패(Rejected) 상태가 된다.
3. 실패 상태가 되면 실패한 이유를 catch()로 받을 수 있다.
- 예제 코드
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;
}
const res = isPositiveP(101);
res
.then((res)=>{console.log("작업 성공 : ", res);})
.catch((err)=>{console.log("작업 실패 : ", err)});
- Promise 사용의 안좋은 예
function taskA(a,b){
return new Promise((resolve, reject)=>{
setTimeout(()=>{
const res = a+b;
resolve(res);
}, 3000);
});
}
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);
})
})
})
- 위 코드는 Promise를 사용하였지만 여전히 콜백 지옥과 비슷한 형태로 메서드를 호출하고 있다.
- Promise then 체이닝
getData(userInfo)
.then(parseValue)
.then(auth)
.then(diaplay);
.....
var userInfo = {
id: 'test@abc.com',
pw: '****'
};
function parseValue() {
return new Promise({
// ...
});
}
function auth() {
return new Promise({
// ...
});
}
function display() {
return new Promise({
// ...
});
}
- 위와 같이 여러개의 Promise를 .then을 통해 간결하게 나타낼 수 있다.
function taskA(a,b){
return new Promise((resolve, reject)=>{
setTimeout(()=>{
const res = a+b;
resolve(res);
}, 3000);
});
}
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)
})
}
// then 체이닝 (결과 값을 변수에 넣어서 사용한다.)
const bPromiseResult = taskA(5,1).then((a_res)=>{
console.log("A RESULT : ", a_res);
return taskB(a_res);
});
bPromiseResult.then((b_res)=>{
console.log("B RESULT : ", b_res);
return taskC(b_res);
}).then((c_res)=>{
console.log("C RESULT : ", c_res);
})
- 위 코드 위에서 Promise를 사용하였지만 콜백 지옥과 유사한 형태를 띄었던 코드를 return값을 Promise 객체로 지정하여 then을 통해 연결 할 수 있도록 수정하였다.
- Promise의 에러 처리 방법
1. then()의 두 번째 인자로 에러를 처리하는 방법
getData().then(
handleSuccess,
handleError
);
2. catch()를 이용하는 방법
getData().then().catch();
function getData() {
return new Promise(function(resolve, reject) {
reject('failed');
});
}
// 1. then()의 두 번째 인자로 에러를 처리하는 코드
getData().then(function() {
// ...
}, function(err) {
console.log(err);
});
// 2. catch()로 에러를 처리하는 코드
getData().then().catch(function(err) {
console.log(err);
- 가급적 프로미스 에러 처리는 catch()를 사용하는 것이 좋다.
- async & await
- 비동기 작업을 매번 콜백 지옥에서 벗어나기 위해 Promise 객체를 작성하는 것도 물론 좋은 방법이지만 Javascript에서는 Promise 객체를 생성하는 것 조차 줄이는 방법이 있다. async를 사용하는 것이다.
- async
- async 키워드는 함수를 선언할 때 붙여줄 수 있다. async 함수는 비동기 작업 그 자체를 뜻한다
1. 함수에 async를 붙인다
2. new Promise... 부분을 없애고 executor 본문 내용만 남긴다.
3. resolve(value); 부분을 return value;로 변경한다.
4. reject(new Error(...)); 부분을 throw new Error(...);로 수정한다.
// 기존
// function startAsync(age) {
// return new Promise((resolve, reject) => {
// if (age > 20) resolve(`${age} success`);
// else reject(new Error(`${age} is not over 20`));
// });
// }
// async로 변경
async function startAsync(age) {
if (age > 20) return `${age} success`;
else throw new Error(`${age} is not over 20`);
}
const promise1 = startAsync(25);
promise1
.then((value) => {
console.log(value);
})
.catch((error) => {
console.error(error);
});
const promise2 = startAsync(15);
promise2
.then((value) => {
console.log(value);
})
.catch((error) => {
console.error(error);
});
- 여기서 알아야 할 것은 async 함수의 리턴 값은 무조건 Promise이다.
* 모든 비동기 동작을 async 함수로 만들 수 있는 건 아니다
- setTimeout 함수 안의 resolve가 있을 경우 setTimeout이 resolve를 호출하기 때문에 (3. resolve(value); 부분을 return value;로 변경한다.)를 보장하지 못한다.
- 때문에 setTimeout의 경우는 async 없이 사용해야 한다.
- await
- await는 Promise가 fulfilled가 되든지 rejected가 되든지 아무튼 간에 끝날 때까지 기다리는 함수다.
- await는 async 함수 내부에서만 사용할 수 있다.
function setTimeoutPromise(delay) {
return new Promise((resolve) => setTimeout(resolve, delay));
}
// 결과값이 필요없는 경우 위와 같이 작성할 수 있으면 결과값은 undefined이다.
async function startAsync(age) {
if (age > 20) return `${age} success`;
else throw new Error(`${age} is not over 20`);
}
async function startAsyncJobs() {
await setTimeoutPromise(1000);
const promise1 = startAsync(25);
try {
const value = await promise1;
console.log(value);
} catch (e) {
console.error(e);
}
const promise2 = startAsync(15);
try {
const value = await promise2;
console.log(value);
} catch (e) {
console.error(e);
}
}
startAsyncJobs();
- startAsyncJobs 함수를 새로 만들었습니다. 이 함수 내에서 await 을 사용하기 위해 async 함수로 정의내린 후, 코드의 마지막 부분에서 호출함으로써 비동기 작업을 시작했습니다. 기존의 then 과 catch 하던 작업들은 모두 이 함수 내에 있다.
1. 문법적으로 await [[Promise 객체]] 이렇게 사용합니다.
2. await 은 Promise 가 완료될 때까지 기다립니다. 그러므로 setTimeoutPromise 의 executor 에서 resolve 함수가 호출될
때까지 기다립니다. 그 시간동안 startAsyncJobs 의 진행은 멈춰있습니다.
3. await 은 Promise 가 resolve 한 값을 내놓습니다. async 함수 내부에서는 리턴하는 값을 resolve 한 값으로 간주하므로,
${age} success 가 value로 들어온다는 점을 알 수 있습니다.
4. 해당 Promise 에서 reject 가 발생한다면 예외가 발생합니다. 이 예외 처리를 하기 위해 try-catch 구문을 사용했습니다.
reject 로 넘긴 에러(async 함수 내에서는 throw 한 에러)는 catch 절로 넘어갑니다. 이로써 익숙한 에러 처리 흐름으로 진행
할 수 있습니다.
- await 은 then 과 catch 의 동작을 모두 자기 나름대로 처리합니다. 그래서 async 함수 내에서 then, catch 메소드의 존재를 잊게 할 수 있다.
- API 호출
async function getData(){
let rawResponse = await fetch('https://jsonplaceholder.typicode.com/posts')
let jsonResponse = await rawResponse.json()
console.log(jsonResponse);
}
getData();
let response = fetch('https://jsonplaceholder.typicode.com/posts')
//fetch는 성공 객체 자체를 보낸다.
.then((res)=>{
console.log(res)
});
- fetch()는 웹페이지에서 리소스를 가져오기 위한 기보적인 API이다.
fetch(url [, options])
.then(response => response.json()) // 또는 다른 방법으로 데이터를 처리
.then(data => {
// 데이터를 사용하는 코드
})
.catch(error => {
// 에러 처리
});
1. url : 가져올 리소스의 URL이다.
2. options : 요청에 대한 다양한 옵션을 설정하는 객체이다.
- 응답 객체(Response)는 여러 속성과 메서드를 가지고 있어서 데이터를 가져오고 처리할 수 있다. 위 예제에서는 response.json() 메서드를 사용하여 JSON 형식의 응답 데이터를 가져온다. 다른 메서드로는 response.text() (텍스트 데이터 가져오기) 와 같은 예가 있다.
- 참고 블로그 https://springfall.cc/article/2022-11/easy-promise-async-await
'Programming > React' 카테고리의 다른 글
React - ReactDOM 초기 root 렌더링 과정 (0) | 2023.09.21 |
---|---|
React - React.js (JSX, State, Props) (0) | 2023.09.15 |
React - React.js 기초 (설치 및 실행) (0) | 2023.09.14 |
React - Node.js 설치 및 사용 (기본, npm) (0) | 2023.09.14 |
Javascript - 기본, 응용(배열 내장 함수, Truthy & Falsy, 삼항 연산자, 단락회로 평가, 조건문 업그레이드, 비 구조화 할당, Spread 연산자) +a (0) | 2023.09.11 |