Node.js 콜백 지옥 탈출기 - Promise 패턴 적용
문제 상황
담당하고 있는 Node.js 6 기반 API 서버의 사용자 인증 로직이 콜백 중첩으로 인해 유지보수가 어려웠다. 데이터베이스 조회, 외부 API 호출, 세션 저장이 순차적으로 이루어지면서 들여쓰기가 5단계까지 깊어진 상태였다.
app.post('/login', (req, res) => {
db.findUser(req.body.email, (err, user) => {
if (err) return res.status(500).send(err);
validatePassword(user, req.body.password, (err, valid) => {
if (err) return res.status(500).send(err);
if (!valid) return res.status(401).send('Invalid');
fetchUserProfile(user.id, (err, profile) => {
if (err) return res.status(500).send(err);
saveSession(user.id, (err, session) => {
if (err) return res.status(500).send(err);
res.json({ user, profile, session });
});
});
});
});
});
Promise 패턴 적용
Node.js 8이 곧 출시될 예정이지만, 현재 환경에서는 Bluebird를 사용해 콜백 기반 함수들을 Promise로 변환했다.
const Promise = require('bluebird');
const db = Promise.promisifyAll(require('./db'));
app.post('/login', (req, res) => {
let userData;
db.findUserAsync(req.body.email)
.then(user => {
userData = user;
return validatePasswordAsync(user, req.body.password);
})
.then(valid => {
if (!valid) throw new Error('Invalid password');
return fetchUserProfileAsync(userData.id);
})
.then(profile => {
return saveSessionAsync(userData.id)
.then(session => ({ user: userData, profile, session }));
})
.then(result => res.json(result))
.catch(err => {
if (err.message === 'Invalid password') {
return res.status(401).send(err.message);
}
res.status(500).send(err);
});
});
결과
에러 핸들링을 catch 블록 하나로 통합할 수 있었고, 비즈니스 로직의 흐름이 명확해졌다. 팀원들과 코드 리뷰에서 긍정적인 반응을 얻어 다른 API 엔드포인트에도 순차적으로 적용할 예정이다.
ES2017의 async/await가 표준화되면 더 개선할 수 있을 것 같다.