콜백이 중첩된 코드는 직관적으로 이해하기 어렵고 가독성이 떨어진다.
-> 그래서 등장한 것이 promise였고, .then()
과 .catch()
문법이 등장했다.
-> ES2017에서는 async
와 await
문법이 등장해서 더욱 간단하게 처리가 가능해졌다.
async function fetchPages() {
try {
const response1 = await fetch(url1)
const response2 = await fetch(url2)
const response3 = await fetch(url3)
} catch (e) {
//...
}
}
콜백보다 프로미스 문법을 사용해야 되는 이유
async function fetchPages() {
const [response1, response2, response3] = await Promise.all([
fetch(url1),
fetch(url2),
fetch(url3),
])
}
병렬로 페이지를 로드할 때는 위와 같이 Promise.all
사용이 가능하다. 프로미스를 모두 이행한 후 반환하며, 프로미스 중 하나라도 거부되면 해당 함수도 거부된다.
Promise.race
도 자주 사용되는데, 아래와 같이 타임아웃을 사용할 때 주로 사용된다. Promise.race
는 가장 먼저 완료되는 것의 결과를 반환한다.
function timeout(millis: number): Promise<never> {
return new Promise((resolve, reject) => {
setTimeout(() => reject('timeout'), millis)
})
}
async function fetchWithTimeout(url: string, ms: number) {
return Promise.race([fetch(url), timeout(ms)])
}
프로미스를 생성하기 보다 async/await를 사용해야하는 이유
async function getJSON(url:string) === function getJSON(url:string) : Promise<any>
async만 붙혀서 사용해도 반환타입을 강제할 수 있기 때문에 항상 비동기 코드를 작성할 수 있다.
type Language = 'JavaScript' | 'TypeScript' | 'Python'
function setLanguage(language: Language) {
/* ... */
}
setLanguage('JavaScript') // OK
let language = 'JavaScript'
setLanguage(language)
// ~~~~~~~~ Argument of type 'string' is not assignable
// to parameter of type 'Language'
위와 같이 language
를 string으로 추론하여 Language 타입으로 할당 불가능한 오류가 발생 할 수 있기 때문에 이럴 때는 타입선언 시에 타입을 let language: Language = 'JavaScript';
과 같이 변경하면 오류가 뜨지 않는다. 또는 language를 let
이 아닌 const
로 상수로 만들면 된다.
type Language = 'JavaScript' | 'TypeScript' | 'Python'
function setLanguage(language: Language) {
/* ... */
}
// Parameter is a (latitude, longitude) pair.
function panTo(where: [number, number]) {
/* ... */
}
panTo([10, 20]) // OK
const loc = [10, 20]
panTo(loc)
// ~~~ Argument of type 'number[]' is not assignable to
// parameter of type '[number, number]'
위에서도 loc
을 number[]로 추론하기 때문에 더 많은 값들을 추가할 수도 있기 때문에 튜플이라고 명시해주어야 에러가 발생하지 않는다.
const loc : [number,number] = [10,20]
과 같이 사용하면 에러가 뜨지 않는다. as const
의 방식도 소개되었지만, 타입 정의에 실수가 있다면 오류가 타입 정의가 아닌 호출되는 곳에서 발생하기 때문에 중첩된 객체에서 오류가 발생할 경우 근본적인 원인을 파악하기 어렵다.
객체 사용 시에도 타입을 명시하거나 상수 단언을 통해 잘못된 추론을 해결할 수 있다.
배열과 객체에 as const 붙였을 때 차이점
const loc = [10, 20] as const
일때는 const loc: readonly [10, 20]
로 추론된다.const ts = {
language: 'TypeScript',
organization: 'Microsoft',
} as const
// 위와 같을 때는 아래처럼 추론된다.
const ts: {
readonly language: 'TypeScript'
readonly organization: 'Microsoft'
}
interface State {
pageText: string
isLoading: boolean
error?: string
}
declare let currentPage: string
// 유효상태만 표현하는 방식으로 변경
interface RequestPending {
state: 'pending'
}
interface RequestError {
state: 'error'
error: string
}
interface RequestSuccess {
state: 'ok'
pageText: string
}
type RequestState = RequestPending | RequestError | RequestSuccess
interface State {
currentPage: string
requests: { [page: string]: RequestState }
}
함수의 매개변수는 타입의 범위가 넓어도 되지만, 결과를 반환할 떄는 타입의 범위가 더 구체적이어야 한다.
interface LngLat {
lng: number
lat: number
}
type LngLatLike = LngLat | { lon: number; lat: number } | [number, number]
interface Camera {
center: LngLat
zoom: number
bearing: number
pitch: number
}
interface CameraOptions extends Omit<Partial<Camera>, 'center'> {
center?: LngLatLike
}
type LngLatBounds =
| { northeast: LngLatLike; southwest: LngLatLike }
| [LngLatLike, LngLatLike]
| [number, number, number, number]
declare function setCamera(camera: CameraOptions): void
declare function viewportForBounds(bounds: LngLatBounds): Camera
위의 경우와 같이 매개변수에는 좀 더 느슨한 LatLatLike
와 같은 타입을 만들어 적용할 수 있다. 그러나 반환형은 가능한 엄격하게 범위를 좁히도록 해야 한다. 이때 Partial<>
이나 Omit<>
과 같은 유틸리티 함수들을 활용하기 유용하다.
함수의 입력과 출력의 타입을 코드로 표현하는 것이 주석보다 더 나은 방법이다.
function getForegroundColor(page?: string) {
return page === 'login' ? { r: 127, g: 127, b: 127 } : { r: 0, g: 0, b: 0 }
}
function getForegroundColor(page?: string): Color {
return page === 'login' ? { r: 127, g: 127, b: 127 } : { r: 0, g: 0, b: 0 }
}
매개변수나 반환형의 타입을 표시해줌을 통해 불필요한 주석을 줄일 수 있다.
/** Does not modify nums */
function sort(nums: number[]) {
/* ... */
}
function sort(nums: readonly number[]) {
/* ... */
}
위와 같이 매개변수를 변경하지 않는다고 주석을 쓰는 것 보다 readonly
를 붙여주면서 규칙을 강제하면 된다.
timeMs
,temperatureC
)function extent(nums: number[]) {
let result: [number, number] | null = null
for (const num of nums) {
if (!result) {
result = [num, num]
} else {
result = [Math.min(num, result[0]), Math.max(num, result[1])]
}
}
return result
}
strictNullChecks
를 설정하면 코드에 많은 오류가 표시되지만 null 값과 관련된 문제점을 찾아낼 수 있기 때문에 반드시 필요하다.interface Layer {
layout: FillLayout | LineLayout | PointLayout
paint: FillPaint | LinePaint | PointPaint
}
// 위의 방식보다는 아래가 낫다.
interface FillLayer {
layout: FillLayout
paint: FillPaint
}
interface LineLayer {
layout: LineLayout
paint: LinePaint
}
interface PointLayer {
layout: PointLayout
paint: PointPaint
}
type Layer = FillLayer | LineLayer | PointLayer
//-------------------------------------------
interface Person {
name: string
// These will either both be present or not be present
placeOfBirth?: string
dateOfBirth?: Date
}
// 주석으로 속성의 관계를 표시하는 것은 위험하다.
interface Person {
name: string
birth?: {
place: string
date: Date
}
}
//------------------------------------------
interface Name {
name: string
}
interface PersonWithBirth extends Name {
placeOfBirth: string
dateOfBirth: Date
}
type Person = Name | PersonWithBirth
출처