https://www.youtube.com/watch?v=ZUAwwUiGo3c
오늘은 타입스크립트 5.2 버전에서 새롭게 소개한 using 키워드에 대해 알아보겠습니다.
typescript의 새로운 키워드 using은 자바스크립트에 새로 도입된 TC39 제안 3단계에 실려 있으며, Symbol.dispose 또는 Symbol.asyncDispose 함수를 가진 객체를 자동으로 해제해주는 기능입니다
tc39
제안이란 ECMAScript 표준에 추가될 새로운 기능들을 제안하는 문서입니다.
tc39
는 ECMAScript 표준을 관리하는 위원회의 이름이기도 합니다. 각 제안은 다음과 같은 5개의 스텝을 거쳐 최종 승인을 받습니다.
앞서 말한 TC39저장소에는 명시적 리소스 관리
라는 이름으로 실려있는데
FileReader 객체
Stream 객체
Database 객체
Network 객체
와 같은 메모리, IO 리소스들을 좀더 명시적인 방법으로 쉽게 해제할 수 있는 문법을 제안합니다.
const reader = stream.getReader();
...
reader.releaseLock(); // Oops, should have been in a try/finally
stream, file reader 객체의 리소스를 release하거나 읽거나 하는 동작들은 에러 발생시 전체 프로그램을 멈출 수 있기 때문에 항상try .. catch
구문 내부에서 사용해야 합니다.
const handle = ...;
try {
... // ok to use `handle`
}
finally {
handle.close();
}
// not ok to use `handle`, but still in scope
위와 같이 try catch 구문을 사용한다고 하더라도 finally 이후에 handle객체가 참조되는 것을 방지할 수 없습니다.
선언된 코드가 handle이 초기화된 곳과 같은 스코프
에 있기 때문입니다.
const a = ...;
const b = ...;
try {
...
}
finally {
a.close(); // Oops, issue if `b.close()` depends on `a`.
b.close(); // Oops, `b` never reached if `a.close()` throws.
}
b리소스는 a리소스와 항상 동일 시점에 해제되어야 하는데 a 리소스 해제가 실패하는경우 b.close()는 실행기회를 항상 잃게 됩니다.
이러한 상황들을 방지하기 위해 자바스크립트에서 리소스를 해제할 때는
리소스의 갯수에 비례하여 코드의 길이가 길어질 수 밖에 없습니다.
// sync disposal
{ // block avoids leaking `a` or `b` to outer scope
const a = ...;
try {
const b = ...;
try {
...
}
finally {
b.close(); // ensure `b` is closed before `a` in case `b`
// depends on `a`
}
}
finally {
a.close(); // ensure `a` is closed even if `b.close()` throws
}
}
// avoids leaking `a` or `b` to outer scope
// ensures `b` is disposed before `a` in case `b` depends on `a`
// ensures `a` is disposed even if disposing `b` throws
using a = ..., b = ...;
await using a = ..., b = ...;
...
함수 객체에 Symbol.dispose라고 하는 global symbol이 속성으로 할당되어 있다면 이 함수는 리소스로 판단되며 유저에 의해 직접 생명주기를 명령형으로 조절(Symbol.dispose)를 호출하거나 선언적으로 using 키워드를 사용해서 관리할 수 있습니다.
import { open } from "fs-sync";
const getFileHandler = () => {
const fileHandler = open("...", "r+");
return {
fileHandler,
[Symbol.Dispose]: () => {
fileHandler.close();
},
};
};
{
using file = getFileHandler("dante.txt");
// 파일 핸들러 사용
file.fileHandler
} // 블록을 벗어나면 리소스 해제
import { open } from "node:fs/promises";
const getFileHandler = async () => {
const fileHandler = await open("...", "r+");
return {
fileHandler,
[Symbol.asyncDispose]: async () => {
await fileHandler.close();
},
};
};
{
await using file = getFileHandler("dante.txt");
// 파일 핸들러 사용
file.fileHandler
} // 블록을 벗어나면 리소스 해제
예를 들어, 리엑트 코드에서 파일을 읽고 쓰는 컴포넌트를 만든다고 가정해보겠습니다. using 키워드를 사용하지 않으면 다음과 같이 작성할 수 있습니다.
import React, { useEffect, useState } from "react";
import { getConnection } from "~/utils";
const NetworkComponent = ({ path }) => {
const [networkHandle, setNetworkHandle] = useState(null);
const [content, setContent] = useState("");
useEffect(() => {
// 파일 핸들을 열고 상태에 저장
const networkHandle = getConnection()
setNetworkHandle(networkHandle.socket);
// 컴포넌트가 언마운트될 때 네트워크 핸들을 닫음
return () => {
networkHandle.socket?.close();
};
}, [path]);
// 파일 내용을 읽어오는 함수
const read = async () => {
const buffer = await networkHandle.read();
setContent(buffer.toString());
};
// 파일 내용을 수정하는 함수
const write = async (newContent) => {
await networkHandle.write(newContent);
setContent(newContent);
};
return (
<div>
<h1>File: {path}</h1>
<button onClick={readFile}>Read</button>
<textarea value={content} onChange={(e) => write(e.target.value)} />
</div>
);
};
using 키워드를 사용하면 다음과 같이 간단하게 작성할 수 있습니다.
// Symbol.dispose 함수를 가진 객체를 반환하는 함수
const getConnection = (host: string, port: number) => {
// ..
return {
socket,
[Symbol.dispose]: () => {
socket.end();
},
};
};
import React, { useEffect, useState } from "react";
import { getConnection } from "~/utils";
const NetworkComponent = ({ path }) => {
const [networkHandle, setNetworkHandle] = useState(null);
const [content, setContent] = useState("");
useEffect(() => {
using networkHandle = getConnection()
setNetworkHandle(networkHandle.socket);
// 컴포넌트가 언마운트될 때 자동으로 네트워크 핸들이 닫힘
}, []);
// 파일 내용을 읽어오는 함수
const read = async () => {
const buffer = await networkHandle.read();
setContent(buffer.toString());
};
// 파일 내용을 수정하는 함수
const write = async (newContent) => {
await networkHandle.write(newContent);
setContent(newContent);
};
return (
<div>
<h1>File: {path}</h1>
<button onClick={readFile}>Read</button>
<textarea value={content} onChange={(e) => write(e.target.value)} />
</div>
);
};
간단한 정리 감사합니다! 빠르게 이해되었네요