동기식 프로그래밍 스타일인 **연속 전달 스타일(CPS)**과 비동기 API를 일반적으로 사용하는 플랫폼으로의 적응은 쉽지 않을 수 있다. 비동기 코드는 구문이 실행되는 순서를 예측하기 어렵게 할 수 있다. 비동기 제어 흐름을 다루기 위해 콜백을 사용할 때 가장 흔히 하는 실수는 콜백 지옥에 빠지거나 코드가 간단한 루틴에서조차 가독성이 떨어지고 유지하기 힘들어져서 수직적이라기보다 수평적으로 커지는 일이다.
이 장에서 배우게 될 것들
클로저와 익명 함수의 in-place 정의는 개발자가 코드베이스 지점들을 옮겨 다니지 않고 원할한 프로그래밍을 하게 해준다. 이는 KISS(Keep It Simple, Stupid) 원칙과 완전하게 부합한다. 이는 코드가 매끄럽게 흘러가게 해주고 이를 짧은 시간에 정의할 수 있게 해준다. in-place 콜백을 만드는 것이 절대적으로 요구되는 것은 아니다. 비동기 프로그래밍과 관련된 문제보다는 규칙 문제가 더 중요하다.
/**
* URL을 입력받아 URL의 내용을 로컬 파일로 다운 받기
*/
import fs from 'fs';
import path from 'path';
import superagent from 'superagent';
import mkdirp from 'mkdirp';
import { urlToFilename } from '../utils.mjs';
export function spider(url, cb) {
const filename = urlToFilename(url);
/**
* 1. 파일 존재 여부 확인 후 해당 url에서 이미 다운로드 했는지 검사
* error 타입이 ENOENT면 파일이 존재하지 않으므로 파일 생성에 문제가 없음
*/
fs.access(filename, err => {
if (err && err.code === 'ENOENT') {
console.log(`Downloading ${url} into ${filename}`);
//2. 파일을 찾을 수 없을 경우
superagent.get(url).end((err, res) => {
if (err) {
cb(err);
} else {
//3. 파일이 저장도리 디렉터리가 있는지 확인
mkdirp(path.dirname(filename), err => {
if (err) {
cb(err);
} else {
//4. HTTP응답의 내용을 파일 시스템에 쓴다.
fs.writeFile(filename, res.text, err => {
if (err) {
cb(err);
} else {
cb(null, filename, true);
}
});
}
});
}
});
} else {
cb(null, filename, false);
}
});
}
콜백 지옥 예시….
많은 클로저와 in-place 콜백 정의가 코드의 가독성을 떨어뜨리고 관리할 수 없는 덩어리로 만드는 상황을 콜백 지옥이라고 한다. Node.js와 JavaScrip에서 일반적으로 가장 잘 알려져 있는 안티 패턴 중 하나이다.
일련의 비동기 작업들의 흐름을 제어하려면 특정 패턴과 기법을 사용해야만 하는 상황이 있다. 특히 외부 라이브러리를 사용하지 않고 일반 JavaScript만 사용하는 경우에는 더욱 그렇다.
비동기 코드를 작성할 때 명심해야 할 첫 번째 규칙은 콜백을 정의할 때 in-place 함수의 정의를 남용하지 않는 것이다. 다음은 중첩수준을 낮게 유지하고 일반적으로도 코드 체계를 개선하는데 도움이 되는 몇 가지 기본 원칙이다.