Vana Blog

Node.js Design Pattern - 콜백을 사용한 비동기 제어 흐름 패턴

April 13, 2019

비동기 프로그래밍의 어려움

모듈화, 재사용성, 유지보수성 같은 특성을 희생시키다 보면 callback 중첩이 확산되어 코드가 엉망이 된다.

웹 스파이더

웹 URL을 입력 받아 URL의 내용을 로컬 파일로 다운로드 하는 기능을 구현해보자.

"use strict";
const request = require('request');
const fs = require('fs');
const mkdirp = require('mkdirp');
const path = require('path');
const utilities = require('./utilities');
function spider(url, callback) {
const filename = utilities.urlToFilename(url);
fs.exists(filename, exists => { //[1]
if(!exists) {
console.log(`Downloading ${url}`);
request(url, (err, response, body) => { //[2]
if(err) {
callback(err);
} else {
mkdirp(path.dirname(filename), err => { //[3]
if(err) {
callback(err);
} else {
fs.writeFile(filename, body, err => { //[4]
if(err) {
callback(err);
} else {
callback(null, filename, true);
}
});
}
});
}
});
} else {
callback(null, filename, false);
}
});
}
spider(process.argv[2], (err, filename, downloaded) => {
if(err) {
console.log(err);
} else if(downloaded){
console.log(`Completed the download of "${filename}"`);
} else {
console.log(`"${filename}" was already downloaded`);
}
});
view raw spider.js hosted with ❤ by GitHub

  1. fs.exists(filename, exists => … 파일이 존재하는지 체크
  2. request(url, (err, response, body) => … 파일이 없으면 URL 다운로드
  3. mkdirp(path.dirname(filename), err => … 그 다음 파일을 저장할 디렉토리가 있는지 확인
  4. fs.writeFile(filename, body, err => … HTTP 응답의 내용을 파일 시스템에 쓴다.

The Callback hell

앞서 정의한 spider() 함수는 여러 수준의 들여 쓰기로 인해 가독성이 떨어진다. 직접 스타일의 블로킹 API를 사용하여 유사한 기능을 구현하는 것이 더 간단할 수 있다. 많은 클로저와 내부 callback 정의가 많고 관리할 수 없을 정도로 된 상황을 콜백 헬 (Callback hell)이라고 한다. 심각한 안티패턴 중 하나이다.

asyncFoo( err => {
  asyncBar( err => {
    asyncFooBar( err => {
      // ...
    })
  })
})

안티패턴의 예

문제는 가독성이다. 추적하기가 어렵다. 또 다른 문제는 각 스코프에서 사용된 변수 이름의 중복이 발생한다는 점이다.(err등) 클로저가 성능 및 메모리 소비 면에서 비용이 적게 든다는 점을 명심해야 한다. 또한 활성 클로저가 참조하는 컨텍스트가 Garbage수집 시 유지되기 때문에 식별하기 어려운 메모리 누수가 발생할 수 있다.


Vana Yun

Written by Vana Yun