JavaScript를 사용하면서 성가신 것 중 하나가 I/O가 일어날 때 비동기 방식으로 동작한다는 점이다. 빈번한 I/O 처리 때문에 때문에, 코딩을 하다 보면 가끔 끔직한 결과를 만나게 될 때가 있는 것 같다. 특히, 연속적인 read, save 할때 말이다. 이를 해결하기 위해서 callback, async module(waterfall), promise 그리고 최근 async-await 까지 나왔다.
async-await 방식이 기존에 방식에 비해 코드 가독성이 높아 쓰인다고 했지만 callback만 써왔기 때문에 async-await 코딩 방식에 익숙하지 않아 연습겸 글을 쓴다.
Node.js로 모바일 앱 서버를 하나 구축하고 있다고 가정하자.
한 회원이 내가 쓴 글 목록을 보는 부분을 구현하고 있다. 인증방식은 로그인 후 받는 auth_key를 통해, 서버의 DB에 저장된 auth_key와 비교 후, 일치하는 auth_key 여만 회원 자신이 작성한 글 목록을 볼 수 있다.
이것을 그냥 한 번에 쭉 callback 형태로 구현하면 다음과 같다.
1. callback1.js
const Member = require('./model/md_member');
const run = (_auth_key) => {
// 1. auth_key로 우리 회원인지 인증한다. 맞으면 id를 내보낸다.
Member
.find( { 'auth_key': _auth_key } )
.select('id')
.exec( (err, result)=> {
if (err) { console.log('DB 에러1'); }
else {
if (result.length === 0) {
console.log('인증실패');
} else {
// 2. id로 회원이 작성한 docs id 배열을 읽는다.
Member
.find({ 'id': result[0].id })
.select('my_docs')
.exec((err, docs) => {
if (err) { console.log('DB 에러2') }
else {
console.log('member docs:',docs[0].my_docs);
}
});
}
}
})
};
run('abcde12345');
보다시피 2개의 함수만 호출했음에도, 함수 run 부분 안이 보기 좋지 않다. 이것을 개선하기 위해 DB에서 find 하는 부분을 분리해본다 .
2. callback.js
const Member = require('./model/md_member');
// 1. auth_key로 우리 회원인지 인증한다. 맞으면 id를 내보낸다.
const getMemberById = (_auth_key, cb_fail, cb_done) => {
Member
.find( { 'auth_key': _auth_key } )
.select('id')
.exec( (err, result)=> {
if (err) { fail(err) }
else {
if (result.length === 0) {
cb_fail(-1);
} else {
cb_done(result[0].id);
}
}
})
}
// 2. id로 회원이 작성한 docs id 배열을 읽는다.
const getMyDocs = (_id, cb_fail, cb_done) => {
Member
.find({ 'id': _id })
.select('my_docs')
.exec( (err, doc) => {
if (err) { fail(err) }
else {
cb_done(doc[0].my_docs);
}
});
}
const run = (_auth_key) => {
getMemberById(_auth_key,
(err)=> {
console.log(' [Err]',err);
// error handling
}, (id)=> {
getMyDocs(id,
(err) => {
console.log(' [Err]',err);
// error handling
}, (docs) => {
console.log('member docs:',docs);
})
});
}
run('abcde12345');
코드는 조금 길어진 것 같으나 깔끔해졌다. 특히 run 부분에서 각 함수가 무엇을 하는지 명확해 진 것 같다.
그렇다면 최종적으로 async-await 로 코딩하면 어떻게 될까?
3. async-await.js
const Member = require('./model/md_member');
// 1. auth_key로 우리 회원인지 인증한다. 맞으면 id를 내보낸다.
const getMemberById = (_auth_key) => {
return new Promise((cb_done, cb_fail) => {
Member
.find( { 'auth_key': _auth_key } )
.select('id')
.exec( (err, result)=> {
if (err) { fail(err) }
else {
if (result.length === 0) {
cb_fail(-1);
} else {
cb_done(result[0].id);
}
}
});
});
}
// 2. id로 회원이 작성한 docs id 배열을 읽는다.
const getMyDocs = (_id) => {
return new Promise((cb_done, cb_fail) => {
Member
.find({ 'id': _id })
.select('my_docs')
.exec( (err, doc) => {
if (err) { fail(err) }
else {
cb_done(doc[0].my_docs);
}
});
});
}
const run = async (_auth_key) => {
try {
let _id = await getMemberById(_auth_key);
let _my_docs = await getMyDocs(_id);
console.log('member_docs:',_my_docs);
} catch (err) {
console.log('[err]:',err);
}
}
run('abcde12345');
run 함수 부분이 보통 익숙한 동기 코드처럼 보인다. 또한 try-catch 구조로, err handling이 간편하다. try 문에서 발생한 err를 catch에서 모두 handling 할 수 있을 것이니 말이다.
가능하다면, 앞으로 가독성이 좋은 async-await 방식으로 코딩을 해야 겠다.
'Programming language > JavaScript' 카테고리의 다른 글
JavaScript, Prototype (0) | 2020.11.14 |
---|---|
자바스크립트, 호출 패턴에 따른 this (0) | 2019.04.08 |
JavaScript Promise의 all( )과 race( ) (0) | 2019.03.23 |
자바스크립트, Promise로 요청실패시 retry 처리하기 (0) | 2019.03.22 |