본문으로 바로가기

using, satisfies in typescript

category software engineering/javascript 2023. 10. 30. 21:25
728x90

1. satisfies

잠깐! 4.9에 나온 satisfies는 사용하고 계신가요?

객체 타입을 검사할 때 생각보다 typescript가 똑똑하게 검사하지 않아서 4.9에 새로 등장했습니다. Typescript에서 설명하기 위한 예시로 palette라는 객체를 예로 들었습니다.

// RGB tuple이나 string을 허용하고 있습니다.
const palette = {
    red: [255, 0, 0],
    green: "#00ff00",
    bleu: [0, 0, 255] // blue에 오타가 있습니다.
};

// 배열의 첫 번째 값을 가져오고 싶지만 string일수도 있어서 허용되지 않습니다.
const redComponent = palette.red.at(0);
// 배열일 수 있어서 이 또한 허용되지 않습니다.
const greenNormalized = palette.green.toUpperCase();

타입은 정의했지만 사용할 떄 문제가 있습니다. 만약 Record로 아래와 같이 타입을 지정한다면 이제 Colors union 타입으로 'blue'의 오타를 찾을 수는 있지만 여전히 redComponent, greenNormalized를 사용할 순 없습니다.

type Colors = "red" | "green" | "blue";
type RGB = [red: number, green: number, blue: number];
const palette: Record<Colors, string | RGB> = ...

이 때 satisfies 키워드를 사용한다면 원하는 오타를 찾고 red와 blue는 tuple타입으로 그리고 green은 string으로 지정할 수 있습니다.

const palette = {
    red: [255, 0, 0],
    green: "#00ff00",
    bleu: [0, 0, 255] // 에러 발견 가능!
} satisfies Record<Colors, string | RGB>;

// 사용 가능!
const redComponent = palette.red.at(0);
const greenNormalized = palette.green.toUpperCase();

 

2. using

본론인 Typescript 5.2에서 새로운 키워드인 'using' 를 살펴보겠습니다.
'
using' 을 사용하면 Symbol.dispose 함수를 통해 스코프를 벗어날 때 모든 것을 폐기할 수 있습니다.

{
  const getResource = () => {
    return {
      [Symbol.dispose]: () => {
        console.log('Hooray!')
      }
    }
  }
  using resource = getResource();
} // 'Hooray!' logged to console

자바스크립트 TC39 Proposal 4단계 중 최근 3단계 해당됩니다. 파일 처리, 데이터베이스 연결과 같은 리소스 관리에 아주 효율적입니다.
TC39 Proposal은 ECMAScript 표준에 추가될 새로운 기능들을 제안하는 문서입니다. (Stage0-Stage4)

Symbol.dispose

새롭게 추가된 자바스크립트 전역 심볼로 Symbol.dispose 함수로 정의된 모든 것들이 특정 수명을 갖는 '리소스'로 간주되며 using 키워드와 함께 사용할 수 있습니다.

const resource = {
  [Symbol.dispose]: () => {
    console.log("Hooray!");
  },
};

await using

비동기적으로 리소스를 폐기하기 위해선 Symbol.asyndDisposeawait using을 사용할 수 있습니다.

const getResource = () => ({
  [Symbol.asyncDispose]: async () => {
    await someAsyncFunc();
  },
});
{
  await using resource = getResource();
}

database connection과 같이 프로그램을 진행하기 전에 연결이 닫혔는지 검증해야할 때 유용합니다.

Use cases

파일 처리

node에서 file handler를 통한 파일 시스템에 접근할 때 using을 사용하면 엄청 쉬워집니다. 만약 using 없이 아래와 같이 구현된 코드를

import { open } from "node:fs/promises";
let filehandle;
try {
  filehandle = await open("thefile.txt", "r");
} finally {
  await filehandle?.close();
}

using을 사용하면 다음과 같이 구현할 수 있습니다.

import { open } from "node:fs/promises";
const getFileHandle = async (path: string) => {
  const filehandle = await open(path, "r");
  return {
    filehandle,
    [Symbol.asyncDispose]: async () => {
      await filehandle.close();
    },
  };
};
{
  await using file = await getFileHandle("thefile.txt");
  // Do stuff with file.filehandle
} // Automatically disposed!

database connection

c#에서도 가장 많이 보여지는 사례입니다. using 없이 사용한다면:

const connection = await getDb();
try {
  // Do stuff with connection
} finally {
  await connection.close();
}

using을 사용한다면:

const getConnection = async () => {
  const connection = await getDb();
  return {
    connection,
    [Symbol.asyncDispose]: async () => {
      await connection.close();
    },
  };
};
{
  await using db = await getConnection();
  // Do stuff with db.connection
} // Automatically closed!

stream, file reader 객체의 리소스를 release하거나 읽거나 하는 동작들은 에러 발생시 전체 프로그램을 멈출 수 있기 때문에 항상try .. catch 구문 내부에서 사용해야 합니다. finally를 사용한다 하더라도 finally 이후에 handle객체가 참조되는 것을 방지할 수 없습니다. 선언된 코드가 handle이 초기화된 곳과 같은 스코프에 있기 때문입니다. 한 개의 리소스가 아닌 여러개의 리소스에서 try .. catch .. finally 또한 문제가 됩니다. 앞으로 using이 명시를 훨씬 쉽게 도와줄 수 있습니다.

 

https://www.totaltypescript.com/typescript-5-2-new-keyword-using

www.totaltypescript.com](https://www.totaltypescript.com/typescript-5-2-new-keyword-using)

https://velog.io/@jay/Typescript-5.2-using(https://velog.io/@jay/Typescript-5.2-using)