비동기 처리란
특정 로직의 실행이 끝날때까지 기다려주지 않고 나머지 코드를 먼저 실행하는 것을 비동기 처리라고 한다.
비동기 처리가 필요한 이유는 무엇일까 화면에서 서버로 데이터를 요청했을 때 서버가 언제 그 요청에 대한 응답을 줄지 모르는데 마냥 기다릴 순 없기 때문이다.
비동기 처리 방식의 문제점은 콜백함수로 해결할 수 있지만, 콜백이 콜백을 무는 형식으로 코드를 짜게되면 코드의 가독성이 떨어지게 되는 콜백 지옥 현상이 나타나게 된다.
동기 방식은 간단하고 직관적이지만, 작업이 오래 걸리거나 응답이 늦어지는 경우에는 전체적인 성능과 사용자 경험에 영향을 줄 수 있다. 따라서 자바스크립트에서는 여러 작업을 동시에 처리하기 위해 비동기라는 개념을 도입하여, 특정 작업의 완료를 기다리지 않고 다른 작업을 동시에 수행할 수 있도록 하였다.
비동기를 알맞게 처리하는 법
만약, 그 다음 실행할 작업이 이전에 요청한 작업의 결과가 반드시 필요한 경우라면 비동기 처리시 문제가 생기는데 이렇게 작업의 순서를 맞추는 것이 필수 불가결일 경우 해결하는 방법 몇가지가 있다.
콜백함수
콜백함수는 함수의 매개변수에 함수 자체를 넘겨서 함수 내에서 매개변수 함수를 실행하는 기법이다. 비동기 방식은요청과 응답의 순서를 보장하지 않기 때문에 응답의 처리 결과에 의존하는 경우 콜백 함수를 이용하여 작업 순서를 간접적으로 끼워 맞출 수 있다. 하지만 콜백 지옥 현상을 조심하자,,,
프로미스 객체
Promise 객체는 비동기 작업의 성공 또는 실패와 그 결과값을 나타내는 객체이다. 그래서 Promise를 사용하면 비동기 작업을 쉽고 깔끔하게 연결할 수 있게 된다.
function getDB() {
return new Promise((resolve) => {
setTimeout(() => {
const value = 100;
resolve(value);
}, 3000);
});
}
function main() {
getDB()
.then((value) => {
let data = value * 2;
console.log('data의 값 : ', data);
})
.catch((error) => {
console.error(error);
});
}
main();
async / await
하지만 promise 객체도 지나친 then 핸들러 함수의 남용으로 인한 Promise Hell이 존재한다. async/await는 프로미스 기반으로 하지만, 마치 동기 코드처럼 작성할 수 있게 해준다. 비동기 작업을 쉽게 읽고 이해할 수 있게 해주기 때문에 비동기 작업을 처리할 일이 있다면 대게 async/await 방식을 쓰는 것이 보통이다.
+추가 Promise.all()
Promise.all은 주로 배열을 인자로 받은 반복가능한 객체들을 순회하면서 비동기 작업을 처리한다. 여러개의 Promise를 동시에 처리하고, 모든 Promise가 완료되었을 때 그 결과들을 반환하는 Javascript의 메서드다. 모든 Promise들이 해결될때까지 기다리고, 모든 Promise가 성공적으로 해결되었을 때, Promise.all()은 해당 Promise들의 결과를 배열로 반환한다.
Promise.all()을 사용하면 병렬로 실행되는 Promise들을 효율적으로 관리하고, 모든 Promise의 결과를 한 번에 처리할 수 있다. 이는 비동기 작업을 동시에 처리하고, 작업이 완료되었을 때 결과를 수집해야 하는 상황에서 유용하다.
exports.getJoinRequest = async (req, res) => {
try {
const userInfo = res.locals.decoded.userInfo;
const userId = userInfo.id;
const groupInfoArray = [];
// 해당 사용자가 리더인 그룹 목록 가져오기
const groups = await Group.findAll({
where: { leader_id: userId },
});
await Promise.all(
groups.map(async item => {
const group = await Group.findByPk(item.group_id);
if (group) {
const members = await UserGroupRelation.findAll({
where: { group_id: group.group_id, request_status: 'a' },
attributes: ['user_id'],
});
const nickNamePromises = await UserGroupRelation.findAll({
where: { group_id: group.group_id, request_status: 'w' },
}).then(members =>
Promise.all(
members.map(async member => {
const user = await User.findOne({
where: { user_id: member.user_id },
});
return user ? { nickName: user.nick_name, userId: member.user_id } : null;
})
)
);
const nickNames = await Promise.all(nickNamePromises);
const groupWithMembers = {
...group.toJSON(),
members: members.map(member => member.user_id),
nickNames, // 닉네임 배열에 닉네임 추가
};
groupInfoArray.push(groupWithMembers);
}
})
);
return res.status(200).send({ isSuccess: true, groups: groupInfoArray });
} catch (err) {
console.error(err);
return res.status(500).send({ isSuccess: false, error: err.message });
}
};
진행한 프로젝트 컨트롤러 함수의 한 예이다.
우선, findOne 메소드를 사용하면 Promise를 리턴하기 때문에 async~ await~를 사용하거나 , then()으로 체이닝 해주어야 한다.
map 함수를 사용하여 얻은 여러 프로미스들을 동시에 실행하기 위해서 Promise.all을 써주었다.
Promise.all() 메소드가 에러를 던지지 않고 정상적으로 종료되었다는 것은 모든 Promise들을 정상적으로 처리했다는 확실한 증거이기 때문에 에러 처리에 신경써서 사용한다면, 성능을 개선할 수 있는 좋은 선택지가 될 수 있을 것 같다.
참고
https://inpa.tistory.com/entry/%F0%9F%8C%90-js-async
'CS' 카테고리의 다른 글
스턱스넷 : Stuxnet 핵이 된 디지털 웜 (0) | 2024.06.21 |
---|---|
Monolithic vs MSA (2) | 2024.04.21 |
RDBMS 와 NoSQL (+짧은 회고) (0) | 2024.01.23 |
URI vs URL (0) | 2024.01.12 |