using 키워드와 Disposable — 리소스 자동 해제 패턴
using키워드는 스코프를 벗어날 때 자동으로 리소스를 해제 하는 TC39 stage 3 제안입니다. TypeScript 5.2부터 지원합니다.
문제: 수동 리소스 관리
// ❌ 파일 핸들이나 DB 연결을 수동으로 닫아야 함
const handle = openFile('data.txt');
try {
const data = handle.read();
processData(data);
} finally {
handle.close(); // 항상 닫아야 함 — 잊기 쉬움
}
using으로 자동 해제
// ✅ using 키워드 — 스코프 끝에서 자동 해제
{
using handle = openFile('data.txt');
const data = handle.read();
processData(data);
} // ← 여기서 자동으로 handle[Symbol.dispose]() 호출
Disposable 인터페이스
using을 사용하려면 객체가 Disposable 인터페이스를 구현해야 합니다.
// 동기 해제
interface Disposable {
[Symbol.dispose](): void;
}
// 비동기 해제
interface AsyncDisposable {
[Symbol.asyncDispose](): Promise<void>;
}
구현 예시
class FileHandle implements Disposable {
private closed = false;
constructor(private path: string) {
console.log(`파일 열기: ${path}`);
}
read(): string {
if (this.closed) throw new Error('이미 닫힌 파일');
return '파일 내용';
}
[Symbol.dispose](): void {
if (!this.closed) {
console.log(`파일 닫기: ${this.path}`);
this.closed = true;
}
}
}
// 사용
function processFile() {
using file = new FileHandle('data.txt');
// 파일 열기: data.txt
const data = file.read();
console.log(data);
// 함수 끝에서 자동: 파일 닫기: data.txt
}
await using (비동기 해제)
class DatabaseConnection implements AsyncDisposable {
private connected = true;
static async connect(url: string): Promise<DatabaseConnection> {
console.log(`DB 연결: ${url}`);
return new DatabaseConnection();
}
async query(sql: string): Promise<any[]> {
if (!this.connected) throw new Error('연결 끊김');
return []; // 쿼리 결과
}
async [Symbol.asyncDispose](): Promise<void> {
if (this.connected) {
console.log('DB 연결 해제');
this.connected = false;
}
}
}
// 사용
async function fetchData() {
await using db = await DatabaseConnection.connect('postgres://localhost:5432');
const users = await db.query('SELECT * FROM users');
console.log(users);
} // ← 자동으로 db[Symbol.asyncDispose]() 호출
DisposableStack
여러 리소스를 함께 관리할 수 있는 컨테이너입니다.
function setupServer() {
using stack = new DisposableStack();
// 여러 리소스를 스택에 추가
const db = stack.use(new DatabasePool());
const cache = stack.use(new CacheConnection());
const logger = stack.use(new FileLogger());
// 추가 정리 로직도 등록 가능
stack.defer(() => {
console.log('서버 종료 정리 완료');
});
return { db, cache, logger };
} // ← 스택에 등록된 모든 리소스가 역순으로 해제됨
실전 활용
임시 파일 관리
class TempFile implements Disposable {
public path: string;
constructor(prefix: string) {
this.path = `/tmp/${prefix}_${Date.now()}`;
// 임시 파일 생성
writeFileSync(this.path, '');
}
[Symbol.dispose](): void {
// 자동으로 임시 파일 삭제
if (existsSync(this.path)) {
unlinkSync(this.path);
}
}
}
function processWithTempFile() {
using temp = new TempFile('upload');
writeFileSync(temp.path, '임시 데이터');
// 처리...
} // ← temp 파일 자동 삭제
락(Lock) 관리
class Lock implements Disposable {
private released = false;
constructor(private resource: string) {
console.log(`락 획득: ${resource}`);
}
[Symbol.dispose](): void {
if (!this.released) {
console.log(`락 해제: ${this.resource}`);
this.released = true;
}
}
}
function criticalSection() {
using lock = new Lock('shared-resource');
// 임계 구역 코드
// ...
} // ← 자동으로 락 해제 (예외가 발생해도)
타이머 / 인터벌
class Interval implements Disposable {
private id: ReturnType<typeof setInterval>;
constructor(callback: () => void, ms: number) {
this.id = setInterval(callback, ms);
}
[Symbol.dispose](): void {
clearInterval(this.id);
}
}
function monitorHealth() {
using healthCheck = new Interval(() => {
console.log('헬스 체크...');
}, 5000);
// 다른 작업...
} // ← 자동으로 인터벌 정리
tsconfig 설정
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022", "ESNext.Disposable"]
}
}
lib에 ESNext.Disposable을 추가해야 Symbol.dispose와 Disposable 인터페이스를 사용할 수 있습니다.
정리
using은 스코프를 벗어날 때[Symbol.dispose]()를 자동 호출한다await using은 비동기 해제([Symbol.asyncDispose]())를 지원한다DisposableStack으로 여러 리소스를 함께 관리할 수 있다- 파일, DB 연결, 락, 타이머 등 정리가 필요한 모든 리소스에 활용 가능하다
- C#의
using, Python의with, Java의 try-with-resources와 유사한 개념이다
댓글 로딩 중...