Programming language/JavaScript

JavaScript callback hell과 async-await

iKay 2017. 11. 1. 17:01
반응형

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 방식으로 코딩을 해야 겠다.

반응형