반응형

헷갈리는 동기, 비동기 개념을 정리하는 포스팅 (2)

 

첫번째 포스팅에서는, callback의 개념과 사용 방법에 대해 다뤘다.

(콜백 지옥을 통해 화살표 함수의 사용법까지 매운맛으로 이해해보았다.)

이전 포스팅🔗 : https://woojin.tistory.com/27

 

 

이번 포스팅은, 비동기 처리를 위한 객체 Promise에 대해

無의 상태로 돌아가서 공부하고 정리해보고자 한다.

 

첫째로는, Promise의 사용법에 대해서 자세히 다룰 것이고

둘째로는, 비동기 처리에서 Promise를 사용하는 방식, 이유에 대해 고찰해보고자 한다.

 

(유튜브 채널 '드림코딩'의 영상 코드를 토대로 공부했다.)

https://youtu.be/JB_yU6Oe2eE

 


Promise란?

정의와 상태에 대해서는 다른 좋은 아티클이 많아 짚고만 넘어가자.

Promise는 비동기 처리를 위한 객체로,

resolve와 reject라는 두 개의 함수를 인자로 전달받는다.

 

Promise 객체가 호출되면 대기(Pending)상태,

Promise 내부 로직을 성공하면 이행(Fulfilled), 실패하면 실패(Rejected)상태가 된다.

 

// 1. Producer
// when new Promise is created, the executor runs automatically.

// Promise를 정의
const testPromise = new Promise((resolve, reject) => {
    // doing some heavy work (network, read files)
    console.log('doing something...');
    setTimeout(() => {
        resolve('woojin');
        // reject(new Error('no network'));
    }, 500);
});

// Promise를 반환
const testPromise = () => new Promise((resolve, reject) => {
    // doing some heavy work (network, read files)
    console.log('doing something...');
    setTimeout(() => {
        resolve('woojin');
        // reject(new Error('no network'));
    }, 500);
});

사용은 위와 같이 하는데, resolve가 성공적으로 실행되면 'woojin'을 반환한다.

실패해서 reject가 실행되면 'no network'라는 메세지를 가진 Error 객체가 반환된다.

(위의 두가지 방법이 모두 사용 가능하다.)

 

반환된 객체의 사용 방법은 아래와 같다.

// 2. Consumers: then, catch, finally
testPromise
    .then(name => console.log(name))
    .catch(error => console.log(error));
    
testPromise
    .then(console.log)
    .catch(console.log);

.then : 함수 하나를 인자로 받고, resolve 값을 가져와서 함수에 집어 넣는다.

(함수를 인자로 받는다 -> 해당 함수를 callback이라고 볼 수 있음)

.catch : 함수 하나를 인자로 받고, reject 값을 가져와서 함수에 집어 넣는다.

 

위 코드블록에서 위에 있는 testPromise

 

반대로 생각하면,

resolve가 실행되면(Promise가 성공하면) .then을 사용할 수 있고

reject가 실행되면(Promise가 실패하면) .catch를 사용할 수 있다.

 

여기서 유의할 점은,

then과 catch는 'woojin'이라는 문자열을 사용하지 않는다.

'woojin'이라는 문자열을 반환하는 Promise객체를 전달받아서 사용하는 것이고, 또다시 Promise객체를 반환한다.

 

이것은 Promise Chaining이라는 활용으로 이어진다.

아래에서 살펴보자.

(일단 여기까지는 비동기랑 Promise랑 뭔 상관인지 잘 모를 것이다. 나는 그랬다.)

 

 

Promise Chaining

then이랑 catch가 Promise 객체를 반환한다고 위에서 언급했다.

이게 뭔 소린지 사실 직관적으로 이해하기는 어려울 것이고,

아래의 활용을 잘 살펴보면 이해가 될 것이다.

 

const fetchNumber = new Promise((resolve, reject) => {
    setTimeout(() => resolve(1), 1000);
});

fetchNumber
    .then(num => num * 2)
    .then(num => num * 3)
    .then(num => {
        return new Promise((resolve, reject) => {
            setTimeout(() => resolve(num - 1), 1000);
        });
    })
    .then(num => console.log(num));

fetchNumber는 호출 시, 1초 후 resolve(1)을 반환하는 Promise 객체이다.

 

fetchNumber가 호출되고, 정상적으로 내부의 setTimeout이 작동했다면

.then 내부에 있는 'num => num * 2' 라는 함수에 1이 전달된다.

 

'num => num * 2' 라는 함수가 정상적으로 작동했다면, resolve(2)를 반환할 것이고

.then(num => num * 3) 을 통해서 불러올 수 있다.

그리고 이것이 정상적으로 작동했다면 resolve(6)을 반환하는데,

 

그 다음 .then을 통해서 resolve(6)을 불러온 후,

'num을 받아 Promise ~~~ 를 반환하는 함수'에 넣는다.

정상적으로 작동한다면, 6을 받아 1초 후 resolve(5)를 반환할 것이다.

 

그 후, resolve(5)가

.then(num => console.log(num)) 으로 전달되어

5가 출력된다.

 

이런 방식을 Promise Chaining이라고 한다.

지난 포스팅에서 다룬 callback만을 사용했다면 굉장히 복잡했을 코드가,

여러 함수를 연쇄적으로 호출했음에도 아주 간단해졌다.

 

 

Error Handling

const getHen = () => 
    new Promise((resolve, reject) => {
        setTimeout(() => resolve('닭'), 1000);
    });

const getEgg = hen => 
    new Promise((resolve, reject) => {
        // setTimeout(() => resolve(`${hen} => 달걀`), 1000);
        setTimeout(() => reject(new Error(`${hen} => 달걀`)), 1000);
    });

const cook = egg => 
    new Promise((resolve, reject) => {
        setTimeout(() => resolve(`${egg} => 프라이`), 1000);
    });

getHen()//
.then(hen => getEgg(hen))
.then(egg => cook(egg))
.then(meal => console.log(meal))
.catch(error => console.log(error));

getHen()이 호출되었다.

setTimeout이 정상적으로 작동한다면, resolve('닭')이 .then을 통해 'hen => getEgg(hen)' 함수로 들어간다.

 

'닭'을 받아 getEgg 내부 로직을 실행하는데,

reject가 실행되었다! 에러가 발생한 것이다.

 

그렇다면 resolve는 실행되지 않고, 'reject(new Error(`${hen} => 달걀`)) 이 반환될 것이다.

하지만 이는 그 다음 .then(egg ...)로 전달되지 않는다.

.then은 resolve를 받아오는 함수이기 때문이다.

 

순서와 관계없이, 마지막에 있는 .catch로 전달되어 `${hen} => 달걀`에 해당하는 '닭 => 달걀'이 출력된다.

 

getHen()//
.then(hen => getEgg(hen))
.catch(error => {
    return '밥';
})
.then(egg => cook(egg))
.then(meal => console.log(meal))
.catch(error => console.log(error));

위처럼 에러가 발생한 지점에서 .catch를 실행할 수 있다.

getEgg에서 에러가 발생했으니 .catch가 실행되는데,

.catch 내부에는 '밥'을 반환하는 함수가 들어 있다.

 

결국 앞에서 실행된 로직과 관계 없이 '밥'이 반환된다.

여기서 유의할 점은!

.then과 .catch는 Promise 객체를 반환한다고 했다.

.catch 내부에서 '밥'을 반환하는 함수가 정상 작동되었으니,

이는 resolve('밥')의 형태로 전달되고, 바로 아래에 있는 .then(egg => cook(egg))에 전달된다!

 

 

callback지옥과 비교

// Callback Hell example

class UserStorage {
    loginUser(id, password, onSuccess, onError) {
        setTimeout(() => {
            if (
                (id === 'woojin' && password === 'hustle') ||
                (id === 'money' && password === 'many')
            ) {
                onSuccess(id);
            } else {
                onError(new Error('not found'));
                console.log(typeof Error);
            }
        }, 2000);
    }

    getRoles(user, onSuccess, onError) {
        setTimeout(() => {
            if (user === 'woojin') {
                onSuccess({ name: 'woojin', role: 'admin' });
            } else {
                onError(new Error('no access'));
            }
        }, 1000);
    }
}

const userStorage = new UserStorage();
const id = 'woojin';
const password = 'hustle1';
userStorage.loginUser(
    id,
    password,
    user => {
        userStorage.getRoles(
            user,
            userWithRole => {
                console.log(`Hello ${userWithRole.name}, you have a ${userWithRole.role} role`);
            },
            error => {
                console.log(error);
            }
        );
    },
    error => {
        console.log(error);
    }
);

지난 포스팅에서 작성한 callback hell 코드이다.

Promise를 활용하면 아래와 같이 간결하게 작성할 수 있다.

 

// Callback Hell example

class UserStorage {
    loginUser(id, password) {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                if (
                    (id === 'woojin' && password === 'hustle') ||
                    (id === 'money' && password === 'many')
                ) {
                    resolve(id);
                } else {
                    reject(new Error('not found'));
                }
            }, 2000);
        });
    }

    getRoles(user) {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                if (user === 'woojin') {
                    resolve({ name: 'woojin', role: 'admin' });
                } else {
                    reject(new Error('no access'));
                }
            }, 1000);
        });
    }
}

const userStorage = new UserStorage();
const id = 'money';
const password = 'many';

userStorage.loginUser(id, password)
    .then(id => userStorage.getRoles(id))
    .then(value => console.log(value))
    .catch(error => console.log(error));


// userStorage.loginUser(
//     id,
//     password,
//     user => {
//         userStorage.getRoles(
//             user,
//             userWithRole => {
//                 console.log(`Hello ${userWithRole.name}, you have a ${userWithRole.role} role`);
//             },
//             error => {
//                 console.log(error);
//             }
//         );
//     },
//     error => {
//         console.log(error);
//     }
// );

여기서 집중할 부분은 하단에 Promise 객체를 호출하는 부분이다.

그 아래에 있는 주석은 callback hell에서 사용했던 코드인데,

Promise chaining으로 간결하게 작성되었다!

 

즉, Promise를 통해 callback hell에서 벗어날 수 있었다.

 

그런데, 이게 비동기랑 도대체 무슨 상관일까?

 

 

비동기와 Promise

Promise의 사용에 대한 수많은 아티클을 보며 사용하는 방법에 대해서 감을 잡을 수 있었다.

그러나 Promise와 비동기 사이에 도대체 무슨 관계가 있는지 정확하게 이해가 되지 않았다.

그래서, 나름대로의 고민을 통해 내린 결론을 적어보려고 한다.

 

우선 비동기처리와 callback의 관계부터 살펴보자.

 

비동기처리라 함은,

A();

B();

와 같은 순서로 코드가 작성되었을 때,

 

A와 B의 처리 시간에 따라

A보다 B의 결과가 먼저 반환될 수도 있는 처리 방식을 의미한다.

 

그런데, B가 A보다 먼저 처리된다는 게 말이 되나?

그렇다 말이 안된다.

코드 순서상 A가 호출된 다음 B가 호출되는 게 맞으니까 당연히 A가 처리되고 B가 처리된다. (JS는 싱글 스레드 언어이므로 한 번에 한 작업을 실행한다.)

 

그렇다면, 비동기 처리라는 것은 B가 먼저 처리되는 것이 아니라,

B의 내부 로직이 A의 내부 로직보다 먼저 처리되는 것이라고 이해해야 한다.

이는 CPU 자원을 분할해서, 메인 스레드에서는 A를 호출하고 B를 호출하면서,

백그라운드에서 A의 내부로직과 B의 내부로직을 처리한다고 이해하면 될 것 같다.

 

따라서,

A(C) {
	C;
}

B(D) {
	D;
}

(A, B, C, D는 모두 함수)

이처럼, A를 호출하면 백그라운드에서 C가 실행되고,

B를 호출하면 백그라운드에서 D가 실행되고,

 

C, D의 처리 시간에 따라 코드 순서와 관계 없이 값을 반환하거나 함수를 처리하는 것이다.

이러한 비동기 처리를 위해 함수의 인자로 함수를 집어넣어 호출하는 callback이 필요했던 것이다.

 

 

그런데, callback은 매우 복잡한 코드를 낳는다.

지난 포스팅에서 쉽게 살펴볼 수 있었다(위에도 코드가 첨부되어 있다.)

 

이를 Promise로 해결했는데,

 

그렇다면 Promise에서는 어떻게 비동기 처리를 구현할까?

 

const fetchReturn = ()  =>
	new Promise((resolve, reject) => {
    	setTimeout(() => resolve(1), 1500);
});

const getHen = () => 
    new Promise((resolve, reject) => {
        setTimeout(() => resolve('닭'), 1000);
});

const getEgg = hen => 
    new Promise((resolve, reject) => {
        setTimeout(() => reject(new Error(`${hen} => 달걀`)), 1000);
});
    

fetchReturn()
	.then(num => console.log(num));

getHen()
	.then(hen => getEgg(hen))
    .then(egg => console.log(egg));

getHen()
	.then(hen => console.log(hen));

위 코드를 통해 간단한 비동기 코드를 이해해보자ㅏ.

 

정의는 건너뛰고 아래 실행 부분을 살펴보면,

1) fetchReturn은 1.5초 후 1을 출력한다.

2) (첫번째)getHen은 1초 후 '닭'을 전달하고, getEgg는 1초 후 '닭 => 달걀'을 출력한다.

(즉, 2초 후 '닭 => 달걀'을 출력한다.)

3) (두번째)getHen은 1초 후 '닭'을 출력한다.

 

Promise는 호출되는 순간 내부 로직을 백그라운드에서 실행시킨다고 이해했다.

그렇기에, 코드 순서가 아닌 처리 시간 순서대로

1

닭 => 달걀

이 출력된다고 이해했다.

 

즉, .then, .catch는 비동기를 이해하기 위함이 아니라

Promise의 작동 방식을 이해하기 위해서 공부한 것이고

 

Promise는 그 자체로 호출되면서부터 그 내부 로직이 비동기처리된다고 이해했다.

(then, catch만 죽어라 공부해도 비동기랑 연관성이 이해가 되지 않았더라는..)

 

 

Typescript 코드로 변환

위에서 사용한 코드를 TS로 변환해보았다.

 

// Promise is a JavaScript object for asynchronous operation.
// State: pending -> fulfilled or rejected
// Producer vs Consumer

// 1. Producer
// when new Promise is created, the executor runs automatically.
const testPromise = new Promise((resolve, reject) => {
    // doing some heavy work (network, read files)
    console.log('doing something...');
    setTimeout(() => {
        resolve('woojin');
        // reject(new Error('no network'));
    }, 500);
});

// 2. Consumers: then, catch, finally
testPromise
    .then(console.log)
    .catch(console.log);

// 3. Promise chaining
const fetchNumber: Promise<number> = new Promise((resolve, reject) => {
    setTimeout(() => resolve(1), 1000);
});

const fetchReturn = (): Promise<number>  =>  new Promise((resolve, reject) => {
    setTimeout(() => resolve(1), 1500);
});


fetchNumber
    .then(num => num * 2)
    .then(num => num * 3)
    .then(num => {
        return new Promise((resolve, reject) => {
            setTimeout(() => resolve(num - 1), 1000);
        });
    })
    .then(num => console.log(num));
    

// fetchReturn()
//     .then(num => num * 2)
//     .then(num => num * 3)
//     .then(num => {
//         return new Promise((resolve, reject) => {
//             setTimeout(() => resolve(num - 1), 1000);
//         });
//     })
//     .then(num => console.log(num));


// 4. Error Handling
const getHen = (): Promise<string> => 
    new Promise((resolve, reject) => {
        setTimeout(() => resolve('닭'), 1000);
    });

const getEgg = (hen: string): Promise<string> => 
    new Promise((resolve, reject) => {
        // setTimeout(() => resolve(`${hen} => 달걀`), 1000);
        setTimeout(() => reject(new Error(`${hen} => 달걀`)), 1000);
    });

const cook = (egg: string): Promise<string> => 
    new Promise((resolve, reject) => {
        setTimeout(() => resolve(`${egg} => 프라이`), 1000);
    });

getHen()//
.then(hen => getEgg(hen))
.catch(error => {
    return '밥';
})
.then(egg => cook(egg))
.then(meal => console.log(meal))
.catch(error => console.log(error));

 

// Callback Hell example

interface role {
    name: string;
    role: string;
}

const admin: role = {
    name: 'woojin',
    role: 'admin',
}

class UserStorage {
    loginUser(id: string, password: string): Promise<string> {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                if (
                    (id === 'woojin' && password === 'hustle') ||
                    (id === 'money' && password === 'many')
                ) {
                    resolve(id);
                } else {
                    reject(new Error('not found'));
                }
            }, 2000);
        });
    }

    getRoles(user: string): Promise<role> {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                if (user === 'woojin') {
                    // resolve({ name: 'woojin', role: 'admin' });
                    resolve(admin)
                } else {
                    reject(new Error('no access'));
                }
            }, 1000);
        });
    }
}

const userStorage = new UserStorage();
const id = 'woojin';
const password = 'hustle';

userStorage.loginUser(id, password)
    .then(id => userStorage.getRoles(id))
    .then(value => console.log(value))
    .catch(error => console.log(error));


// userStorage.loginUser(
//     id,
//     password,
//     user => {
//         userStorage.getRoles(
//             user,
//             userWithRole => {
//                 console.log(`Hello ${userWithRole.name}, you have a ${userWithRole.role} role`);
//             },
//             error => {
//                 console.log(error);
//             }
//         );
//     },
//     error => {
//         console.log(error);
//     }
// );

 

 

다음 포스팅에서는 비동기 처리의 끝판왕,

async, await에 대해서 다뤄보겠다!

 

 

 

반응형
반응형

 

헷갈리는 동기, 비동기 개념을 정리하는 포스팅 (1)

 

첫번째 포스팅에서는,

callback이 무엇인지,

어떻게 쓰이는지,

그리고 화살표 함수를 제대로 해석하는 방법까지 정리해보고자 한다.

 

참고한 영상 : https://youtu.be/s1vpVCrT8f4

 


callback이란?

1. 다른 함수(또는 코드)의 인자로서 활용되는 함수.

2. 어떤 이벤트에 의해 호출되는 함수.

 

쉽게 말하면, 함수의 인자처럼 전달되어서 쓰이는 함수를 의미한다.

 

const print = () => console.log('hi');

setTimeout(print, 2000)

print는 아무 인자도 전달받지 않고 'hi'를 출력하는 함수이다.

setTimeout은 함수 하나(print)와 숫자(ms)를 전달받아서,

숫자 만큼의 시간이 지난 후 함수(print)를 호출하는 함수이다.

 

여기서 이 print라는 함수가 callback이 된다.

setTimeout이라는 함수의 인자로 쓰여서, 2000ms가 지나는 이벤트 후에 호출되었기 때문이다.

이렇게 callback이라는 것은 (아주 비약하자면) 함수의 인자처럼 쓰이는 함수이다.

 

 

callback함수의 활용과 callback지옥 (화살표 함수에 대한 이해)

실행결과는 아래와 같다

실행 2초 후 위와 같은 문자열이 출력된다.

 

'woojin', 'hustle'이라는 id와 password 그리고

onSuccess(user를 넣으면 getRoles에 user를 전달해서 실행하는 함수)

onError(error를 넣으면 error를 출력하는 함수)

이렇게 4개의 인자를 loginUser에 전달해서 실행시킨다.

 

여기서 onSuccess, onError에서 정의한 함수가 callback이다.

 

onError는 loginUser에 전달되는데, 

loginUser에서는 onError에 'not found'라는 문자열을 출력하는 new Error 객체를 전달하여 실행시키게 되어 있다.

 

관계가 복잡하지만,

화살표 함수의 용법과 콜백의 정의, 쓰임에 대해 정확하게 이해할 수 있는 코드이다.

 

하지만, 저렇게 해석하기 어려운 코드는 이해하기에도, 유지보수에도 불리하다.

 

위처럼 콜백이 콜백을 부르며 코드 왼쪽 공백의 깊이가 깊어지는 것을 콜백 지옥이라고 부른다.

 

다음 포스팅에서는, 콜백 지옥을 해결할 수 있는 방법인

Promise의 활용에 대해서 다뤄보겠다.

 

 

+) Javascript로 작성된 위 코드를 Typescript로 변환해보았다.

// Callback Hell example

interface success {
    name: string;
    role: string;
}

const obj: success = {
    name: 'woojin',
    role: 'admin',
}

class UserStorage {
    loginUser(
        id: string,
        password: string,
        onSuccess: (x: string) => void,
        onError: (x: object) => void) {
        setTimeout((): void => {
            if (
                (id === 'woojin' && password === 'hustle') ||
                (id === 'money' && password === 'many')
            ) {
                onSuccess(id);
            } else {
                onError(new Error('not found'));
            }
        }, 2000);
    }

    getRoles(user: string, onSuccess: (x: success) => void, onError: (x: object) => void) {
        setTimeout(() => {
            if (user === 'woojin') {
                // onSuccess({ name: 'woojin', role: 'admin' });
                onSuccess(obj);
            } else {
                onError(new Error('no access'));
            }
        }, 1000);
    }
}

const userStorage = new UserStorage();
const id: string = 'woojin';
const password: string = 'hustle';
userStorage.loginUser(
    id,
    password,
    (user: string) => {
        userStorage.getRoles(
            user,
            (userWithRole: success) => {
                console.log('hi')
                console.log(`Hello ${userWithRole.name}, you have a ${userWithRole.role} role`);
            },
            error => {
                console.log(error);
            }
        );
    },
    error => {
        console.log(error);
    }
);

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

반응형
반응형

이전 포스팅에서는

아주 기본적인 형태의 라우팅을 적용해보았다.

(+ 라우팅이 무엇인지)

🔗 이전 포스팅 링크 : https://woojin.tistory.com/25

 

[Express] 간단한 라우팅 적용하기 (Typescript 사용) #Node.js #Express

이전 포스팅에 이어서, 간단한 라우팅을 적용해보자. (이전 포스팅) Express 간단한 서버 세팅 👇 https://woojin.tistory.com/24 라우팅(Routing)이란? 애플리케이션 엔드포인트(URI)의 정의, 그리고 URI가 클

woojin.tistory.com

 

이번 포스팅에서는

유지보수가 용이하도록

라우터 관련 코드를 적절히 분리시켜보자.

 


 

디렉토리 구조 만들기

위와 같이 디렉토리 구조를 설계한다.

빈껍데기인 파일만 만들어 놓아도 된다.

 

src/api : 블로그 관련 api를 묶어놓은 폴더

src/router : api 폴더에 있는 api들의 라우팅을 담당하는 폴더

 

blog.ts, signup.ts, user.ts는 간단한 api를 구현한 것으로, 아래에서 상세 내용을 다루겠다.

 

 

 

디렉토리 구조 설명

대략적인 디렉토리 구조를 설명하고 넘어가보려 한다.

 

localhost:8080/api/user/1 에 GET 메서드를 요청한다면,,

 

1. index.ts로 이동한다.

2 .api를 보고 router/index.ts로 이동한다.

3. user를 보고 router/userRouter.ts로 이동한다.

4. get 메서드를 보고 파라미터(userId : 1)를 가지고 api/user.ts 에서 정의한 selectUser로 이동한다.

5. api/user.ts에서 정의한 selectUser를 실행한다.

 

쉽게 말하면,

api에서는 api 기능에 대한 내용을 다루고,

router에서는 메서드 종류와 요청에 대한 분기를 다룬다.

 

내용에 따라 폴더가 분리되어 있어, 쉽게 위치를 파악하고 유지보수할 수 있다.

 

 

api 폴더 작성

// blog.ts

import { Request, Response } from "express";

const selectPost = async (req: Request, res: Response) => {
    const param = req.params;

    return res.status(200).json({
        status: 200,
        message: "포스팅 조회 성공",
        posting_number: param.blogId
    });
};

const likePost = async (req: Request, res: Response) => {
    const param = req.params;

    return res.status(200).json({
        status:200,
        message: "좋아요 성공",
        posting_number: param.blogId
    });
};

export { selectPost, likePost };

blog.ts에서는 selectPost와 likePost 두 개의 함수를 정의한다.

req.params는 뒤에서 정의할 request url 상 파라미터를 가져오는 것이다.

 

// signup.ts

import { Request, Response } from 'express';

const signup = async (req: Request, res: Response) => {
    return res.status(201).json({
        status: 201,
        message: "회원가입 성공",
    });
}

export default signup;
// user.ts
import { Request, Response } from "express";

const selectUser = async (req: Request, res: Response) => {
    const param = req.params

    return res.status(200).json({
        status: 200,
        message: "유저 조회 성공",
        data: param
    });
};

export default selectUser;

json 형식의 메세지와 상태코드를 응답(response)한다.

 

 

router 폴더 작성

// blogRouter.ts

import express, { Router } from "express";
import { likePost, selectPost } from "../api/blog";

const router: Router = express.Router();

router.get('/:blogId', selectPost);
router.get('/like/:blogId', likePost);

export default router;

Router를 따로 import해놓고, express.Router()를 사용하는 것은

에어비엔비 표준 규격을 맞추기 위함이라고 한다.

 

코드에서 보이다시피, 특정 엔드포인트에 특정 메서드가 요청되면, api 폴더에 작성한 파일의 함수가 불러와진다.

 

엔드포인트 뒤에 ':blogId'와 같은 것은 url로 전달받은 파라미터를 저장할 변수명이다.

예를 들어 블로그 id가 3인 포스팅을 'likePost'하려고 한다면,

localhost:8080/api/blog/like/3 과 같이 요청을 받아

3을 'blogId'에 저장해서 likePost 함수가 있는 api/blog.ts로 전달하는 것이다.

 

// signupRouter.ts

import express, { Router } from 'express';
import signup from '../api/signup';

const router: Router = express.Router();

router.post('/', signup);

export default router;
// userRouter.ts

import express, { Router } from "express";
import selectUser from "../api/user";

const router: Router = express.Router();

router.get('/:userId', selectUser);

export default router;

 

마지막으로 index.ts를 만들어준다 (router 폴더 내부에 있는 index.ts)

// router/index.ts

import express, { Router } from "express";

import userRouter from "./userRouter";
import blogRouter from "./blogRouter";
import signupRouter from "./signupRouter";

const router: Router = express.Router();

router.use('/user', userRouter);
router.use('/blog', blogRouter);
router.use('/auth', signupRouter);

export default router;

위에서 작성한 파라미터 앞단에 붙는 url을 정의한다.

/user는 userRouter로 이동하고 (..)

 

이렇게 정의한 router는,

아래에서 루트 디렉토리에 있는 index.ts로 전달한다.

 

 

index.ts(루트 디렉토리) 작성

// index.ts

import express, { Request, Response, NextFunction } from 'express';
import apiRoute from './router/index'; // export한 이름과 다르게 import할 수 있다!

const app = express();

app.use(express.json());

app.use('/api', apiRoute);

app.get('/', (req: Request, res: Response, next: NextFunction) => {
    res.send('Hi! This is server tutorial.');
});

app.listen('8080', () => {
    console.log(`
    #############################################
        🛡️ Server listening on port: 8000 🛡️
    #############################################
    `);
});

 

이제 라우팅 구조가 명확하게 보일 것이다.

아래 예시들에 설명을 달아보자.

 

1. localhost:8080/ (get)

index.ts의 app.get에 따라 'Hi! This is server tutorial.'이 response로 보내진다.

실행 결과는 위와 같다.

 

2. localhost:8080/api/user/1 (get)

index.ts에서,

app.use('/api', apiRoute) 를 보고 apiRoute를 import한 ./router/index.ts로 이동한다.

 

./router/index.ts에서,

router.use('/user', userRouter)에 의해 userRouter를 import한 ./router/userRouter.ts로 이동한다.

 

./router/userRouter.ts에서,

router.get('/:userId', selectUser)로 이동해 userId:1 을 selectUser로 전달한다.

 

./api/user.ts에서,

selectUser가 실행되고, userId:1 은 req.params를 통해 전달된다.

실행 결과는 위와 같다.

 

 

 

간단한 라우팅 구조에서

유지보수가 용이한 라우터 분리 구조로 수정해보았다.

 

 

 

 

 

 

 

 

 

 

 

 

 

반응형
반응형

 

 

이전 포스팅에 이어서, 간단한 라우팅을 적용해보자.

 

(이전 포스팅)

Express 간단한 서버 세팅 👇

https://woojin.tistory.com/24

 

 

 

 


라우팅(Routing)이란?

애플리케이션 엔드포인트(URI)의 정의, 그리고 URI가 클라이언트 요청에 응답하는 방식을 말한다.

route의 동명사형인데, 이거 콩글리시 아닌가? (콩글리시 죽어)

route는 '루트'로 발음한다. 우리가 흔히 아는 '경로'라는 단어가 맞다.

 

쉽게 말하면, 요청이 URI에 들어오면 그것을 어느 경로로 분기시켜줄지 즉, 어디로 보내줄지 정의하는 것을 의미한다.

 

 

Express의 라우팅 기본 구조

src 폴더에 index.ts가 있다.

src 폴더에 api 폴더를 만든다.

api 폴더에는 index.ts, user.ts를 생성한다.

 

localhost:8000/api/user에 클라이언트의 요청이 들어오면

우선 ../src/index.ts 로 간다.

그곳에서 분기되어 ..src/api/index.ts 로 간다.

그곳에서 user.ts로 가라는 명령을 받아 ..src/api/user.ts로 이동한다.

 

즉, 요청은 가장 처음 ../src/index.ts 로 이동하고,

각 폴더의 index.ts 들의 명령을 통해 정확한 위치로 이동하여 요청을 수행한다.

 

자, 이제 간단한 라우팅을 구현해보자.

 

 

assignment/src/api/user.ts 생성

* 프로젝트명 : assignment

// user.ts
import express, { Request, Response, Router } from 'express';

const router: Router = express.Router();

router.get('/', (req: Request, res: Response) => {
    return res.status(200).json({
        status: 200,
        message: '유저 조회 성공'
    });
});

module.exports = router;

 

 

get 메서드를 간단하게 정의했다.

json response를 router 객체에 담고, 모듈로 반환한다.

 

 

assignment/src/api/index.ts 생성

// index.ts

import express, { Router } from 'express';

const router: Router = express.Router();

router.use('/user', require('./user'));

module.exports = router;

/api/user 엔드포인트에 요청이 들어오면 user.ts 파일을 실행하게 한다.

 

 

assignment/src/index.ts 수정

/api 로 요청이 들어왔을 때, src/api/index.ts 로 보내 올바르게 분기하도록 설정해야한다.

지금은 요청이 src/index.ts 로 들어왔을 때, src/api/index.ts로 보내주도록 설계되지 않았다.

 

// ../src/index.ts

import express, { Request, Response, NextFunction } from 'express';

const app = express();

app.use(express.json());	// request body를 express에서 json으로 받아 온다.

app.use('/api', require('./api'));	// /api 엔드포인트에 요청이 들어오면 api 폴더로 분기한다.

app.get('/', (req: Request, res: Response, next: NextFunction) => {
    res.send('Hi! This is my first express server. My name is Woojin.');
});

app.listen('8000', () => {
    console.log(`
    #############################################x
        🛡️ Server listening on port: 8000 🛡️
    #############################################    
    `)
})

 

 

localhost:8000/api/user 접속

브라우저에 localhost:8000/api/user 를 입력하고 접속한다.

서버가 구동되고 있지 않다면, 터미널에 'yarn run dev'를 실행시킨다.

 

../src/api/user.ts에 작성한 대로 응답을 받을 수 있다.

이렇게 간단한 라우팅이 완료되었다.

 

 

 

 

반응형
반응형

Express로 아주 간단한 서버를 만들어 초기 세팅하고,

라우팅하는 방법까지 정리해보고자 한다.

(라우팅은 다음 포스팅에서)

 

 


package.json 생성

// terminal 실행
yarn --init

위와 같이 이름만 입력하고 엔터로 넘기면 package.json 파일이 생성된다.

 

 

Express, TS용 모듈 설치

yarn add express	// express 설치
yarn add -D @types/node @types/express	// typescript용 모듈 설치
yarn add -D nodemon	// 서버 코드 변경 시 자동 재시작을 도와주는 nodemon

 

 

Express 세팅하기

tsc --init	// tsconfig.json 파일 생성

Typescript를 JS로 컴파일하는 옵션을 설정할 수 있다.

 

 

서버 만들기

express 프로젝트 폴더 내부에 src 폴더를 만들고,

그 내부에 index.ts 파일을 만든다.

import express, { Request, Response, NextFunction } from 'express';

const app = express();	// express 객체 받아옴

app.get('/', (req: Request, res: Response, next: NextFunction) => {
    res.send('Hi! This is my first express server');
});	// HTTP GET method 정의

app.listen('8000', () => {
    console.log(`
    #############################################
        🛡️ Server listening on port: 8000 🛡️
    #############################################    
    `)
})	// 8000번 포트에서 서버 실행

 

프로젝트 루트 디렉토리에 'nodemon.json' 파일을 만든다.

// nodemon.json

{
    "watch": [ 
        "src",
        ".env"
    ], 
    "ext": "js,ts,json",
    "ignore": [
        "src/**/*.spec.ts"  
    ],
    "exec": "ts-node  --transpile-only ./src/index.ts"
}

nodemon은 서버 실행 및 동기화 역할이다.

watch: 변경 감지 경로

ext: 변경 감지 확장자

ignore: 변경 감지 제외

exec: nodemon 수행 명령

 

nodemon을 실행하기 위해 'package.json'을 수정하자.

// package.json

{
  "name": "express-example",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
    "dev": "nodemon",
    "build": "tsc && node dist"
  },
  "devDependencies": {
    "@types/express": "^4.17.13",
    "@types/node": "^17.0.25",
    "nodemon": "^2.0.15"
  },
  "dependencies": {
    "express": "^4.17.3"
  }
}

dev 명령어로 nodemon을 실행하고

build 명령어로 dist 폴더에 js로 트랜스파일된 파일을 만들 수 있다.

yarn run dev	// nodemon으로 서버 실행
yarn run build	// 프로젝트 build

 

 

 

서버 실행

터미널에 yarn run dev 를 실행한다.

서버가 실행된다.

 

브라우저에 'localhost:8000'을 입력하고 접속한다.

 

서버가 정상적으로 구동되고 있음을 확인할 수 있다.

 

nodemon의 정상 구동을 확인하기 위해

../src/index.ts 에서 코드를 수정해본다.

// ../src/index.ts

import express, { Request, Response, NextFunction } from 'express';

const app = express();

app.get('/', (req: Request, res: Response, next: NextFunction) => {
    res.send('Hi! This is my first express server. My name is Woojin.');
});	// 문구 수정!!

app.listen('8000', () => {
    console.log(`
    #############################################
        🛡️ Server listening on port: 8000 🛡️
    #############################################    
    `)
})

수정 후 저장하면, 터미널에서 서버가 재시작됨을 볼 수 있다.

 

브라우저도 새로고침해보자.

정상적으로 변경되었다!

 

 

 

프로젝트 build

터미널에 아래와 같이 실행시킨다.

yarn run build

dev와 마찬가지로 서버가 실행되는데,

'dist'라는 폴더가 생성됨을 확인할 수 있다.

dist 폴더에는 TS로 작성한 파일이 JS로 트랜스파일되어 저장되는데, node로 서버를 실행하기 위함이다.

실제 서버를 배포할 때는 dist 폴더에 있는 파일들이 배포된다.

(참고 : 트랜스파일이란 비슷한 추상화 수준을 가진 언어로의 컴파일, 컴파일에 속하는 개념)

 

 

 

 

 

 

다음 포스팅에서는, 방금 세팅한 서버를 기반으로

라우팅을 정의해보자.

 

 

 

 

 

반응형
반응형

Yarn이란?

npm을 보완하여 나온 패키지 매니저

 

Express란?

Node.js를 위한 서버 프레임워크

 

 

 

 

1. 설치

npm install yarn -g

yarn --version

yarn을 글로벌로 설치하고, 설치 완료 여부 확인을 위해 버전을 확인한다.

package.json 파일이 설치되었을 것이다.

 

 

2. 초기 세팅

폴더를 생성한 후 디렉토리를 이동한 다음, yarn --init 을 실행한다.

name만 입력해주고 나머지는 enter로 넘긴다.

 

3. Express 설치

yarn add express	// express 설치
yarn add -D @types/node @types/express	// typescript용 모듈 설치
yarn add -D nodemon	// 서버 코드 변경 시 자동 재시작을 도와줌
tsc --init 	// tsconfig.json 파일 생성, TS를 JS로 컴파일하는 옵션 설정

기본으로 생성되는 tsconfig.json을 수정할 필요가 있다. (검색 참고)

// tsconfig.json

{
  "compilerOptions": {
    "target": "es6", // 어떤 버전으로 컴파일
    "allowSyntheticDefaultImports": true, // default export가 없는 모듈에서 default imports를 허용
    "experimentalDecorators": true, // decorator 실험적 허용
    "emitDecoratorMetadata": true, // 데코레이터가 있는 선언에 대해 특정 타입의 메타 데이터를 내보내는 실험적인 지원
    "skipLibCheck": true, // 정의 파일 타입 체크 여부
    "moduleResolution": "node", // commonJS -> node 에서 동작
    "module": "commonjs", // import 문법
    "strict": true, // 타입 검사 엄격하게 
    "pretty": true, // error 메시지 예쁘게
    "sourceMap": true, // 소스맵 파일 생성 -> .ts가 .js 파일로 트랜스 시 .js.map 생성
    "outDir": "./dist", // 트랜스 파일 (.js) 저장 경로
    "allowJs": true, // js 파일 ts에서 import 허용
    "esModuleInterop": true, // ES6 모듈 사양을 준수하여 CommonJS 모듈을 가져올 수 있게 허용
    "typeRoots": [
      "./src/types/express.d.ts", // 타입(*.d.ts)파일을 가져올 디렉토리 설정
      "./node_modules/@types" // 설정 안할시 기본적으로 ./node_modules/@types
    ]
  },
  "include": [
    "./src/**/*" // build 시 포함
  ],
  "exclude": [
    "node_modules", // build 시 제외
    "tests"
  ]
}

 

 

4. nodemon.json 생성

// nodemon.json

{
    "watch": [
        "src",
        ".env"
    ],
    "ext": "js,ts,json",
    "ignore": [
        "src/**/*.spec.ts"  
    ],
    "exec": "ts-node  --transpile-only ./src/index.ts"
}

watch : 변경 감지 경로

ext : 변경 감지 확장자

ignore : 변경 감지 제외

exec : nodemon 수행 명령

 

5. package.json 수정

// package.json

{
  "name": "express-example",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
    "dev": "nodemon",
    "build": "tsc && node dist"
  },
  "devDependencies": {
    "@types/express": "^4.17.13",
    "@types/node": "^17.0.25",
    "nodemon": "^2.0.15"
  }
}

 

6. src폴더와 그 내부에 index.ts 생성

// index.ts

import express, { Request, Response, NextFunction } from 'express';

const app = express();

app.get('/', (req: Request, res: Response, next: NextFunction) => {
    res.send('Hi! This is my first express server');
});

app.listen('8000', () => {
    console.log(`
        #############################################
        🛡️ Server listening on port: 8000 🛡️
        #############################################  
    `);
})

 

7. 서버 실행 및 프로젝트 빌드

yarn run dev	// package.json에 따라 nodemon 실행

yarn run build	// package.json에 따라 tsc && node dist 실행

build 명령어 실행 시 'dist' 폴더가 생성된다.

그 안에는 ts를 js로 변환한 트랜스 파일, sourceMap 파일이 생성된다.

 

지금까지 세팅한 결과로 위와 같은 디렉토리 구조가 생성된다.

반응형
반응형

비동기처리란?

작업의 완료 유무와 상관 없이 동시에 다음 작업을 수행하는 처리 방식을 말한다.

(동기처리는 작업이 반드시 순서대로만 수행되는 처리 방식을 의미한다.)

 

비동기처리를 수행하기 위해 TS(JS)에서는 Callback Function, Promise, Async-Await 등을 이용한다.

각각이 비동기처리를 어떻게 수행하는지 순서대로 알아보자.

 

 

 

 

Callback Function

Callback Function은 특정 조건에서 실행되는 함수로, 특정 시간에 도달하거나 특정 이벤트가 실행됐을 때 시스템에서 호출하는 함수이다.

간략하게만 알아보자.

console.log('Hello Server');

setTimeout((): void => {
    console.log('Hello Server 2');
}, 3000); // 3초

console.log('Hello Server 3');

setTimeout()은 특정 시간 이후에 콜백 함수를 실행시키는 내장함수이다.

만약 위 코드가 동기처리된다면 Hello Server, 2, 3 순서대로 출력되겠지만,

위와 같이 비동기처리된다.

 

위와 같은 Callback Function을 활용한 비동기처리의 문제점은 코드가 복잡해진다는 점이다.

let serverList: string[] = [];

setTimeout((name): void => {
    serverList = [...serverList, name];
    console.log(serverList);
    
    setTimeout((name): void => {
        serverList = [...serverList, name];
        console.log(serverList);
        
        setTimeout((name): void => {
            serverList = [...serverList, name];
            console.log(serverList);
        }, 500, 'name3');
    }, 500, 'name2');   
}, 500, 'name1');

위와 같은 코드는 안으로 심하게 파여 흔히 '콜백 지옥(Callback hell)'이라고 표현한다.

 

 

 

 

Promise

Callback hell을 극복하기 위해 만들어진 것으로,

Pending(대기), Fulfilled(이행), Rejected(실패)의 3가지 상태가 있다.

const condition: boolean = true;

const promise = new Promise((resolve, reject) => {
    if (condition) {
        setTimeout(() => {
            resolve('Success');    
        }, 1000);
        // resolve('Success');
    } else {
        reject(new Error('Error! Condition is false'))
    }
});

promise
    .then((resolveData): void => console.log(resolveData))
    .catch(err => console.log(err));

console.log('test')

위 코드를 통해 Promise의 사용 방법을 알 수 있다.

우선 new Promise를 통해 promise에 Promise 객체를 할당한다.

condition이 true이므로 setTimeout이 실행되는데,

promise.then에서 then은 resolve가 실행된 이후 호출된다.

 

즉 setTimeout()의 조건에 따라 1초가 지나기 전이 Pending(대기)상태이고, 이때제일 아래에 있는 console.log('test')가 실행된다.

이후 1초가 지나 resolve가 실행되면 'Success' 값이 then으로 전달되고 그것을 resolveData라는 매개변수로 받는다.

이 상태가 Fulfilled(이행) 상태이다.

 

만약 condition이 false였다면 reject가 실행되었을 것이고, catch가 호출된다. 이 상태를 Rejected(실패) 라고 한다.

 

조금 복잡한 형태의 코드를 통해

Promise Chaining에 대해 알아보자. (Promise를 연쇄적으로 호출하는 것을 의미한다.)

 

const restaurant = (callback: () => void, time: number) => {
    setTimeout(callback, time);
};

const order = (): Promise<string> => {		// order 객체가 string을 반환하는 Promise 객체라는 뜻이다.
    return new Promise((resolve, reject) => {	// Promise 객체를 반환한다
        restaurant(() => {						// Promise 내부 함수
            console.log('레스토랑 진행 상황 - 음식 주문');
            resolve('음식 주문 시작');			
        }, 1000);
    });
}

const cook = (progress: string): Promise<string> => {	// cook은 string인 progress라는 매개변수를 받아, string을 반환하는 Promise 객체라는 뜻.
    return new Promise((resolve, reject) => {
        console.log('레스토랑 진행 상황 - 음식 조리');
        restaurant(() => {
            resolve(`${progress} -> 음식 조리 중`);	// 전달받은 progress에 텍스트를 붙여 resolve로 반환한다.
        }, 2000);
    });
}

const serving = (progress: string): Promise<string>  => {
    return new Promise((resolve, reject) => {
        console.log('레스토랑 진행 상황 - 음식 서빙');
        restaurant(()=> {
            resolve(`${progress} -> 음식 서빙 중`);
        }, 3000);
    });
}

const eat = (progress: string): Promise<string> => {
    return new Promise((resolve, reject) => {
        console.log('레스토랑 진행 상황 - 음식 먹기');
        restaurant(() => {
            resolve(`${progress} -> 음식 먹는 중`);
        }, 4000);
    });
}

order()	// order() 실행
    .then(progress => cook(progress))	// order 내부에서 resolve가 실행되어 반환되는 값이 progress로 들어간다. progress => cook(progress)는 함수를 then의 매개변수로 넣은 것.
    .then(progress => serving(progress))
    .then(progress => eat(progress))
    .then(progress => console.log(progress));

progress가 연쇄적으로 전달된다.

 

 

 

 

Async - Await

ES2017부터 제공되는 혁신적인 기능! 이라고 한다.

코드를 짤 때 동기식 코드처럼 사고하고 코딩할 수 있게 되었다.

 

우선 함수 선언부터 알아보자.

// 함수 표현식
const asyncFunction1 = async () => {

}

// // 함수 선언식
async function asyncFunction2() {

}

위와 같이 선언한다.

 

let asyncFunc1 = (msg: string): Promise<string> => {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve(`asyncFunc1 - ${msg}`);
        }, 1000);
    });
};

let asyncFunc2 = (msg: string): Promise<string> => {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve(`asyncFunc2 - ${msg}`);
        }, 1500);
    });
};

const asyncMain = async (): Promise<void> => {
    let result = await asyncFunc1('hi');
    console.log(result);
    result = await asyncFunc2('hello');
    console.log(result);
};

const promiseMain = (): void => {
    asyncFunc1('hi').then((result: string) => {
        console.log(result);
        return asyncFunc2('hello');
    }).then((result: string) => {
        console.log(result);
    });
};

asyncMain();

promiseMain();

await은 Promise가 실행될 때까지 기다리겠다는 뜻이다.

익숙한 동기 코드의 실행 방식처럼, 실행 여부를 then, catch로 분기하지 않고 코딩할 수 있다.

 

 

 

 

 

 

반응형
반응형

 

타입스크립트(Typescript)란?

자바스크립트(Javascript)와 100% 호환되는 언어로, 이름처럼 type이 중요해진 JS라고 볼 수 있다.

 

JS에서는 변수의 타입을 지정하지 않는다.

let a = 3;
const PI = 3.14;

이 경우, 변수의 타입이 잘못 지정되거나 외부에서 받아온 데이터의 타입을 잘못 이해해서 오류가 발생할 수 있다.

따라서, 사용하기는 조금 까다롭지만 안전한 프로그래밍이 가능한 TS의 인기가 올라가고 있다.

 

JS의 사용이 자유로워 제한하는 문법이 추가된 TS, 문법이 다소 딱딱한 JAVA를 사용하는 Kotlin이 대화를 나누고 있다.

 

 

 

 

타입 지정하기

let isDone: boolean = true;
const str: string = 'hello';
let num: number = 2;

위와 같이 '변수명: 타입' 으로 타입을 지정한다.

 

let array: number[] = [1, 2, 3]; // Array<number>

const strArr: Array<string> = ['hello', 'world'];

const objArr: Array<object> = [
    { item: 'value1' },
    { item: 'value2' }
];

배열은 위와 같이 지정한다.

 

objArr.map((obj: any) => {
    console.log(`item: ${obj.item}`)
})

/*
*item: value1
*item: value2
*/

map() 메서드를 활용하면 배열을 순회하면서 요소들을 호출한다.

'any'는 모든 타입을 사용할 수 있다.

 

const foo = (obj: object): void => {
    console.log(obj);
};

const boo = (obj: Object): void => {
    console.log(obj);
}

// foo('hi')
// Argument of type 'string' is not assignable to parameter of type 'object'.

boo('hi')

object : 원시 타입(숫자형, 문자형, 불린형)이 아닌 타입만 할당 가능

Object : 모든 타입 할당 가능

 

function foo2(a: number, b: number): number {
    return a + b;
};

const boo2 = (a: number, b: number): number => {
    return a + b;
};

함수의 타입 지정은 위와 같이 한다.

 

let a: null = null;
// let x: null = 2;

let b: undefined = undefined;
// let y: undefined = null;

null과 undefined도 타입으로 지정해야하고, 동일한 이름의 값만 할당할 수 있다.

 

let myName: any = 'Woojin';
let myNameLength: number = (<string>myName).length;

let yourName: any = 'Lee';
let yourNameLength: number = (yourName as string).length;

any는 상술했듯 모든 타입을 할당할 수 있는데, 대신 변수의 타입을 알 수 없다.

length 함수처럼 특정 타입이어야만 사용할 수 있는 것들에는 위와 같은 방법으로 타입을 지정해줘야 한다.

(근데 지정 안해도 typeof 로는 string이라고 나온다. 뭐지?)

 

 

 

 

인터페이스(interface)

객체 지향 언어인 JS.

객체(Object)를 많이 사용하는데, TS에서는 객체의 타입을 지정할 수 있는 방법을 만들어놓았다.

인터페이스는 객체의 프로퍼티들의 타입을 지정해놓은 틀의 개념이다.

 

interface Closet {
    name: string;
    shirt: number;
    pants: number;
    sunglass?: number; <?: 선택적 프로퍼티>
    hat?: number;
}

위와 같이 객체에 지정할 인터페이스를 선언한 다음,

 

const ohmygirl: Array<Closet> = [
    {
        name: '효정',
        shirt: 5,
        pants: 2,
    },
    {
        name: '아린',
        shirt: 2,
        pants: 8,
        hat: 2 // 선택적!
    }
];

객체의 타입을 위에서 선언한 인터페이스로 지정한다.

선택적 프로퍼티는 인터페이스 선언 시 물음표를 키값에 붙인다.

해당 프로퍼티는 객체에 존재해도 되고 안 해도 된다.

 

interface Sopt {
    season: number;
    group: string[];
    part: string[];
    president: string;
    introduce(number): string[];
}

const currentSopt: Sopt = {
    season: 30,
    group: ['YB', 'OB'],
    part: ['King the Server', 'Plan', 'Design', 'Android', 'Web', 'iOS'],
    president: 'Kim',
    introduce: function (this.season) {	// 매개변수는 예시를 위해 넣었고, 쓰이지 않음.
        return this.part.map(name => {
            console.log(`솝트 내 파트는 ${name} 등이 있어요!`);
            return `솝트 내 파트는 ${name} 등이 있어요!`
        });
    }
};

위와 같이 인터페이스에 '함수'도 프로퍼티로 넣을 수 있다.

매개변수와 결과값의 타입을 지정해야 한다.

매개변수가 없다면 공백을, 결과값이 없다면 void를 넣어준다.

 

interface Member {
    name: string;
    group: string;
}

interface Dinner {
    member: Member[];
    shuffle: {
        (array: Array<Member>): Array<Member>;
    };
    organize: {
        (array: Array<Member>): void;
    };    
}

위의 코드처럼, 인터페이스 안에 인터페이스를 사용할 수도 있다.

shuffle이라는 함수의 매개변수 array의 타입을 Array<Member>로 지정하고

그 결과값도 Array<Member>로 지정한 코드이다.

 

 

* 본 포스팅은 SOPT 30th 서버 파트 1주차 세미나를 듣고 복습을 위해 작성한 글입니다.

서팟장 잡채님 만세.

 

반응형

+ Recent posts