2019-07-14

[TypeScript] Promise.all에는 인자 개수 제한이 없다

트위터에서 이상한 글을 봤다. TypeScript가 선언한 Promise.alliterable을 받지 않고 인자 개수 제한이 있다는 것이다. 근데 그럴 리가. TypeScript쯤 되는 프로젝트에서 무언가가 스펙과 다른 것으로 보인다면, 코드를 잘못 이해했을 확률이 높다. 코드를 다시 확인해보자.

당연히 iterable을 인자로 받는 Promise.all 선언도 있다. 다만, 이 선언이 es2015.promise.d.ts가 아닌 es2015.iterable.d.ts에 있을 뿐이다. 타입스크립트 컴파일러 옵션 중 --lib 으로 사용할 라이브러리를 지정할 수 있다. 대부분 es2015, es2018 같은 식으로 버전을 지정하여 포함하는 것이 일반적이라 잘 알려지지 않았지만, es2015.symbolSymbol에 대한 선언만 포함하거나, es2015.promisePromise에 대한 선언만 포함하는 것이 가능하다. 각 라이브러리는 서로 간에 의존성이 없도록 작성돼 있으며, 특히 iterable에 관련된 선언은 전부 es2015.iterable에서 선언한다. 예를 들어 Map이나 Setes2015.collection에 선언돼 있지만, iterable을 반환하는 entries, values, keys 등의 선언은 전부 es2015.iterable에 선언돼 있다. 마찬가지로 iterable을 인자로 받는 Promise.all 함수도 es2015.iterable에 선언돼있다.

iterable을 받는 Promise.all 함수 선언은 es2015.iterable에 선언돼 있는데, es2015.promise에 10개가 더 선언돼있다. 이들은 무엇일까? 사실 이 함수들은 배열을 인자로 받는 함수가 아니라 tuple을 인자로 받는 함수 선언이다. JavaScript에는 임의 개수의 원소를 가지는 배열은 있지만, 고정된 개수의 원소를 가지는 tuple은 없다. 그래서 tuple이 필요할 때 배열을 사용한다. 이를 TypeScript에서는 별도의 타입으로 표시할 수 있도록 했다. T라는 타입의 배열은 T[]로 표시하고, T1, T2, T3라는 타입의 원소 3개를 가지는 tuple은 [T1, T2, T3]로 표현하는 식이다. 즉, es2015.iterable에는 iterable을 인자로 받는 Promise.all을 선언하고, es2015.promise에는 tuple을 인자로 받는 함수를 선언한 것이다.

그렇다면 왜 길이가 11개 이상인 tuple을 인자로 받고 싶을 때는 어떻게 해야 할까? 11개 짜리 tuple을 받는 함수는 선언되지 않았기 때문에 iterable로 해석하여 결과 값을 tuple이 아닌 배열로 돌려준다. 반환되는 배열의 타입은 tuple 각 원소의 union type이다. 다시 말해서 타입 정보를 잃는다. 강제로 타입 변환을 하거나 타입 가드를 사용하면 되지만 이는 코드를 verbose하게 만든다.

그럴 때는 그냥 11개 이상인 tuple을 인자로 받는 함수를 선언해서 사용하면 된다. Promise의 전역 함수 all을 선언하는 것이 아닌 PromiseConstructor의 함수 all을 선언하는 것만 기억하면 된다. Promise.race도 같은 방식으로 확장할 수 있다.

하지만 사용할 수 있는 tuple의 크기를 확장하기 전에 한 번 더 고민해봐야 한다. tuple은 각 원소가 무엇을 의미하는지 코드에 명시적으로 드러나지 않고 숨겨져 있다. 이런 암시적인 코드는 수정 시 놓치기 쉽고, 버그의 원인이 된다. 개인적으로는 10개짜리 tuple도 크다고 생각한다. tuple의 크기는 3개만 넘어가도 다른 표현법이 없는지 찾아봐야 한다. 하지만 절대적인 것은 아니다. tuple로밖에 처리하지 못할 때에는 새 선언을 이용해 함수를 확장해서 사용하면 된다.

댓글 1개:

  1. 좋은 글 써주셔서 감사합니다. :) 저도 오해하도록 글을 두서 없이 썼네요. 좋은 정보 알려주셔서 감사합니다.

    답글삭제