Callback 많을 때 Async 사용하기

동시에 수행되는 비동기 응답을 처리하려고 callback 함수 여럿 사용했더니 금세 코드가 어지러워졌다. 이 코드는 도서관 홈페이지를 읽어서 책 정보를 읽어오는 기능의 일부다. booklist로 전달된 책들에 대하여 대출되었는지 확인하여 그 결과를 getBook 함수에게 딱 한번 전달하는 것이다. 검색 별로 종료 시점을 알지 못해서 모든 검색의 종료를 확인하기 위해 checkPoint, checkPointLimt 변수를 사용하고 있어서 복잡하다. 어느 부분이 동시에 수행되는지 알기 힘들다. 자세히 보면 동시에 수행되는 코드 내부에 딱 한번 수행되는 코드가 섞여 있다.

Before

checkPointLimit = booklist.length;
 
appendBookId(booklist, body);
 
_.each(booklist, function(book, key) {
  var o = _.clone(opt);
  o.bookId = book.bookId;
  var index = key;
 
  searchDetail(o, function (err, exist) {
    checkPoint = checkPoint + 1;
    if (err) {
      exist = false;
    }
    booklist[index].exist = exist;
    if (checkPoint === checkPointLimit) {
      if (getBook) {
        getBook(null, {
          startPage: opt.startPage,
          totalBookCount: totalBookCount,
          booklist: booklist
        });
      }
    }
  });
});

하나의 책 검색이 종료될 때마다 checkPoint를 증가시키다가 이 값이 checkPointLimit 값에 이르면 모든 검색을 마쳤다고 판단한다. _.each부터 시작하면 callback 함수가 중첩되어 호출되는데 이해하기 어렵다. Promise 혹은 Generator 사용 대신 라이브러리를 찾기로 했다. 현재 이 코드가 Node.js v4.0.0에서 사용되고 있는 것도 라이브러리를 찾게 되는 이유 중에 하나다. Async 모듈을 사용하여 코드를 읽기 쉽게 변경했다.

$ npm install --save async

After

appendBookId(booklist, body);

var tasks = [];

_.each(booklist, function(book, key) {
  var o = _.clone(opt);
  o.bookId = book.bookId;
  tasks.push(function (callback) {
    searchDetail(o, function (err, exist) {
      if (err) {
        exist = false;
      }
      callback(null, exist);
    });
  })
});

async.parallel(tasks, function (err, results) {
  if (err) {
    getBook({msg: 'Error, Can\'t access detail information'});
    return;
  }
  if (getBook) {
    booklist = _.map(booklist, function (item, key) {
      item.exist = results[key];
      return item;
    });
    getBook(null, {
      startPage: opt.startPage,
      totalBookCount: totalBookCount,
      booklist: booklist
    });
  }
});

코드는 조금 더 늘어났지만 깊이는 얕아졌다. Async 모듈에서 Async.parallel 이라는 함수 하나를 사용한다. Before 코드의 음영 부분이 After 코드에서 두 부분으로 분리되어 코드 읽기가 좋아졌다. 우선, 코드에서 동시에 수행되는 부분을 명확하게 알 수 있다. tasks 변수에 저장된 여러 함수들을 async.parallel가 동시에 수행하며 이 결과는 async.parallel 2번째 callback 함수에 인자로 전달된다. 그리고 모든 검색이 종료되는 시점을 조사할 필요가 없다. checkPoint, checkPointLimit 변수가 필요 없다. async.parallel 함수가 이 문제를 해결한다. 이 함수의 2번째 인자로 전달된 callback 함수가 불려지는 시점이 검색 종료 시점이다. 또한 여러 검색 결과를 따로 저장할 필요가 없다. async.parallel 2번째 callback 함수의 2번째 인자인 results에 모든 검색 결과가 저장되어 있다. 그전에 왜 사용하지 않았나 싶다.

답글 남기기

아래 항목을 채우거나 오른쪽 아이콘 중 하나를 클릭하여 로그 인 하세요:

WordPress.com 로고

WordPress.com의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Google photo

Google의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Twitter 사진

Twitter의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Facebook 사진

Facebook의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

%s에 연결하는 중

This site uses Akismet to reduce spam. Learn how your comment data is processed.