Promise의 정의
Promise는 자바스크립트 비동기 처리에 사용되는 객체입니다. 여기서 자바스크립트의 비동기 처리란 ‘특정 코드의 실행이 완료될 때까지 기다리지 않고 다음 코드를 먼저 수행하는 자바스크립트의 특성'을 의미하며, Promise 객체를 사용해 순서를 보장해 줄 수 있습니다. 하지만 비동기처리를 위해 Promise를 잘못사용하면 성능에 좋지 못한 결과를 초래할 수 있습니다.
Promise의 병렬처리란?
Promise의 병렬처리란 동시에 여러 Promise를 실행하는 것을 의미하는데. 동시에 완료해야 하는 서로 연관성이 없는 독립적인 비동기작업을 수행할때 유용하게 사용합니다. 자바스크립트는 Promise를 병렬로 실행하기 위한 몇 가지 method를 제공하는데, 이 method를 상황에 맞게 잘할 경우 애플리케이션 성능향상에 큰 도움이 됩니다.
여기 유저정보를 받아오는 Promise 함수 getData함수가 있습니다.
이 getData함수는 type이라는 변수를 받아서 data객체에 값을 찾아 리턴해주는 함수입니다.
이 함수 내부의 data 객체가 할당되어 있는데, 이를 DB에 저장되어 있는 데이터라 칭하겠습니다.
함수는 한번 실행될때마다 type이라는 매개변수를 받아 DB의 값을 찾아서 받아오기까지 시간이 1초 걸리는 쿼리작업수행합니다.
이 getData 함수를 잘 사용할 수 있도록 유저정보를 받을 수 있는 getUser와 회사정보 콘텐츠정보를 각각 가져오는 getCompany, getContent까지 총 3가지의 async, await 함수를 모듈화 했고,
클라이언트의 요청에 따라 각 함수는 user, company, content의 정보를 내려줄 예정입니다.
1. for loof 비동기처리
그런데 클라이언트는 모든 정보를 받기를 원합니다. 그래서 모든 데이터를 긁어와 제공하는 work라는 함수를 만들어 각각의
정보를 제공해 주기로 했습니다.
for of loop문 안에서도 위의 코드와 마찬가지로 순서가 보장된 비동기 작업을 처리할 수 있습니다.
순서가 보장된 비동기처리로 인해 각각의 함수는 이전 작업이 마칠 때까지 모두 기다리기에 총 3초에 시간이 걸렸습니다.
생각보다 너무 오래 걸리는 작업이므로 리팩토링이 필요해 보입니다.
각각의 함수들은 다른 promise객체의 리턴값을 참조하여 작업을 하지 않는 독립적인 함수이므로 다른 메서드활용이 가능해 보입니다.
2.Promise.all
각각의 작업은 서로 상관 관계없는 독립 작업이므로 Promise.all메서드의 사용을 고려해 볼 수 있는 상황입니다.
Promise.all은 배열의 모든 Promise를 병렬로 실행합니다. 즉, 다음 Promise를 시작하기 전에 하나의 Promise가 해결될 때까지 기다리지 않습니다. 이로 인해 특히 동시에 완료해야 하는 여러 독립적인 비동기 작업을 수행할 때 성능이 향상될 수 있습니다. 아래의 코드는 이전코드를 Promise.all로 리팩터링 한 코드이며 결괏값은 다음과 같습니다.
3.Promise.all이 만능인가? 모든 작업의 성공을 보장할 수 없다면??
Promise.all이 만능은 아닙니다. 다음과 같은 상황이 벌어졌다고 가정해 봅니다.
type 매개변수로 할당할 수 있는 값은 user, company, content인데 video라는 없는 값을 할당했습니다.
앞서 작성한 getdata함수는 매개변수로 올 수 없는 값은 rejected으로 리턴하는 Promise 함수였습니다.
Promise 함수중 rejected으로 반환되는 값이 하나라도 발생할 경우, 다른 작업이 정상적으로 작동되더라도 값을 return 받을 수 없습니다.
또한 배열의 순서대로 작업이 끝난다는 보장을 받을 수 없는
전체 작업 중 한 개의 작업이라도 이행되지 않는다면 모든 작업이 종료되는 단점이 있기에 클라이언트에서 에러를 맞이할 수도 있으므로,
이럴 때는Promise.allSettled 메서드를 활용하면 에러상황에도 코드가 지속되는 단점을 해결할 수는 있습니다.
4.Promise.allSettled
Promise.allSettled는 Promise 배열을 가져와서 배열의 모든 Promise가 성공유무와는 상관없이 무조건 resolve 되는 메서드입니다. Promise의 resolve 된 값은 객체 배열 형태이며, 여기서 각 객체는 각 Promise의 status와 value, reason을 나타냅니다.
Promise.allSettled는 각각의 값이 객체배열로 리턴되는데 각 객체의 값에는 이행완료여부를 확인하는 status, 이행이 완료될 경우에는 value에 return값이 할당되고, reject이 될 경우 reason에 reason값이 할당되어 반환됩니다.
작업의 이행유무와는 무관하게 병렬처리는 완료되어 클라이언트에 에러상황을 줄일 수 있는 장점이 있으며. 처리속도나 에러핸들링에 있어서는 비교적 최적화해 낼 수 있습니다. 하지만 별도의 에러핸들링 처리를 하지 않을 경우 디버깅이 쉽지 않습니다.
그렇다면 어디서 무엇을 써야 할까??
결론적으로, Promise의 병렬 처리는 자바스크립트 애플리케이션의 성능을 최적화시켜 주는 효과적인 메서드라 생각합니다. 하지만 등의 각각의 상황에 따른 올바른 접근 방식이 필요해 보입니다.
- 순서의 보장이 필요한가?
for loop 기반의 비동기병렬처리
- 서로 연관관계나 참조값이 필요 없는 독립적인 업무인가?
1. 무조건적인 성공을 보증하는가 or 모든 작업이 꼭 성공해야만 하는 서비스인가? Promise.all
2. 실패가 되더라도 코드의 흐름이 멈추어서는 안 되는 서비스인가? Promise.allSettled
전체 코드보기
const getData = async ({ type }) => {
const data = {
user: 'cram',
company: 'cramCompany',
content: 'cramVideo',
};
return new Promise((resolve, reject) => {
switch (type) {
case 'user':
case 'company':
case 'content':
setTimeout(() => resolve(data[type]), 1000);
break;
default:
setTimeout(() => reject(), 1000);
break;
}
});
};
const getUser = async () => {
const user = await getData({ type: 'user' });
return user;
};
const getCompany = async () => {
const company = await getData({ type: 'company' });
return company;
};
const getContent = async () => {
const content = await getData({ type: 'content' });
return content;
};
const work1 = async () => {
try {
console.time('work1 시간');
const resArr = [];
for (const type of ['user', 'company', 'content']) {
const data = await getData({ type });
resArr.push(data);
}
console.timeEnd('work1 시간');
console.log('data', resArr);
return resArr;
} catch (err) {
console.log(err);
}
};
work1();
const work2 = async () => {
try {
console.time('work2 시간');
const [user, company, content] = await Promise.all([
getUser(),
getCompany(),
getContent(),
]);
console.timeEnd('work2 시간');
console.log('data', { user, company, content });
return { user, company, content };
} catch (err) {
console.log(err);
}
};
work2();
// const work3 = async () => {
// try {
// console.time('work3 시간');
// const [user, company, content] = await Promise.all(
// ['user', 'company', 'video'].map((type) => getData({ type }))
// );
// console.timeEnd('work3 시간');
// console.log('data', { user, company });
// return { user, company, content };
// } catch (err) {
// console.log(err);
// }
// };
// work3();
// const work4 = async () => {
// try {
// console.time('work4 시간');
// const result = await Promise.allSettled(
// ['user', 'company', 'video'].map((type) => getData({ type }))
// );
// console.timeEnd('work4 시간');
// console.log('data', result);
// } catch (err) {
// console.log(err);
// }
// };
// work4();
'WEB > Node.js' 카테고리의 다른 글
[Node.js]REPL,모듈,노드내장객체 (0) | 2022.03.16 |
---|---|
[Node.js] 호출스택, 이벤트루프 (0) | 2022.03.03 |
[NodeJS]-1. 노드 정의 및 특성 (0) | 2022.02.24 |
[JavaScript] for in 과 for of 반복문 그리고 enumerable, iterable 속성 (0) | 2021.12.17 |
[JavaScript]Promise 와 async/await (0) | 2021.12.15 |