[Typescript] exercise 10

박세진·2023년 4월 12일
0

원티드 타입스크립트 챌린지를 듣고, 추천 받은 공부 방법 중에 exercise를 통해서 1부터 10까지 풀 수 있어야 된다고 해서, exercises를 풀면서 타입스크립트를 학습해보기로 했다.

사이트 : TypeScript Exercises

목표로 잡아두었던 typescript exercise 10...!
10번 문제는 호락호락하지 않았다...🥺 → 😭

(이번 게시글에는 따로 목차별로 나누지 않고 문제와 문제풀이를 같이 작성했습니다)

exercise 10

문제 10 : 우리는 모든 데이터를 요청하는 함수를 다시 구현하고 싶지 않습니다. 기존 콜백 기반 함수에 새로운 Promise 호환 결과를 장식합시다. 최종 함수는 최종 데이터(users 또는 admins)로 직접 resolve할 수 있는 Promise를 반환하거나 오류(또는 type Error)로 reject 해야 한다. 이 함수는 promisify라는 이름으로 지어야 된다.

심화 문제 : 함수를 포함하는 객체를 허용하는 promisifyAll 함수를 만드십시오. 각 함수가 promisified 된 새 객체를 반환한다. api 생성을 다시 작성해야 됨 const api = promisifyAll(oldApi) 이렇게!

interface User {
    type: 'user';
    name: string;
    age: number;
    occupation: string;
}

interface Admin {
    type: 'admin';
    name: string;
    age: number;
    role: string;
}

type Person = User | Admin;

const admins: Admin[] = [
    { type: 'admin', name: 'Jane Doe', age: 32, role: 'Administrator' },
    { type: 'admin', name: 'Bruce Willis', age: 64, role: 'World saver' }
];

const users: User[] = [
    { type: 'user', name: 'Max Mustermann', age: 25, occupation: 'Chimney sweep' },
    { type: 'user', name: 'Kate Müller', age: 23, occupation: 'Astronaut' }
];

export type ApiResponse<T> = (
    {
        status: 'success';
        data: T;
    } |
    {
        status: 'error';
        error: string;
    }
);

export function promisify(arg: unknown): unknown {
    return null;
}

const oldApi = {
    requestAdmins(callback: (response: ApiResponse<Admin[]>) => void) {
        callback({
            status: 'success',
            data: admins
        });
    },
    requestUsers(callback: (response: ApiResponse<User[]>) => void) {
        callback({
            status: 'success',
            data: users
        });
    },
    requestCurrentServerTime(callback: (response: ApiResponse<number>) => void) {
        callback({
            status: 'success',
            data: Date.now()
        });
    },
    requestCoffeeMachineQueueLength(callback: (response: ApiResponse<number>) => void) {
        callback({
            status: 'error',
            error: 'Numeric value has exceeded Number.MAX_SAFE_INTEGER.'
        });
    }
};

export const api = {
    requestAdmins: promisify(oldApi.requestAdmins),
    requestUsers: promisify(oldApi.requestUsers),
    requestCurrentServerTime: promisify(oldApi.requestCurrentServerTime),
    requestCoffeeMachineQueueLength: promisify(oldApi.requestCoffeeMachineQueueLength)
};

function logPerson(person: Person) {
    console.log(
        ` - ${person.name}, ${person.age}, ${person.type === 'admin' ? person.role : person.occupation}`
    );
}

async function startTheApp() {
    console.log('Admins:');
    (await api.requestAdmins()).forEach(logPerson);
    console.log();

    console.log('Users:');
    (await api.requestUsers()).forEach(logPerson);
    console.log();

    console.log('Server time:');
    console.log(`   ${new Date(await api.requestCurrentServerTime()).toLocaleString()}`);
    console.log();

    console.log('Coffee machine queue length:');
    console.log(`   ${await api.requestCoffeeMachineQueueLength()}`);
}

startTheApp().then(
    () => {
        console.log('Success!');
    },
    (e: Error) => {
        console.log(`Error: "${e.message}", but it's fine, sometimes errors are inevitable.`);
    }
);

promisify 함수는 인수에 oldApi 메서드를 받고 있다.

function promisify<V>(callback: (response: ApiResponse<V[]>) => void) {
    console.log(callback)
}

일단 promisify에 들어오는 것을 확인해보았다.

requestAdmins(callback) {
        callback({
            status: 'success',
            data: admins
        });
    }

Log를 확인해보니 이렇게 들어오고 있는 상태였다.

일단 async / await 구문을 사용한 코드를 봤다.

async function startTheApp() {
    console.log('Admins:');
    (await api.requestAdmins()).forEach(logPerson);
    console.log();

    console.log('Users:');
    (await api.requestUsers()).forEach(logPerson);
    console.log();

    console.log('Server time:');
    console.log(`   ${new Date(await api.requestCurrentServerTime()).toLocaleString()}`);
    console.log();

    console.log('Coffee machine queue length:');
    console.log(`   ${await api.requestCoffeeMachineQueueLength()}`);
}

api.requestAdmins() 함수를 호출할 때, async / await을 사용했기 때문에 api 객체의 각 프로퍼티는 promise를 반환하는 함수여야 된다.


const api = {
    requestAdmins: promisify<Admin[]>(oldApi.requestAdmins),
    requestUsers: promisify<User[]>(oldApi.requestUsers),
    requestCurrentServerTime: promisify<number>(oldApi.requestCurrentServerTime),
    requestCoffeeMachineQueueLength: promisify<number>(oldApi.requestCoffeeMachineQueueLength)
};

const oldApi = {
    requestAdmins(callback: (response: ApiResponse<Admin[]>) => void) {
        callback({
            status: 'success',
            data: admins
        });
    },
    requestUsers(callback: (response: ApiResponse<User[]>) => void) {
        callback({
            status: 'success',
            data: users
        });
    },
    requestCurrentServerTime(callback: (response: ApiResponse<number>) => void) {
        callback({
            status: 'success',
            data: Date.now()
        });
    },
    requestCoffeeMachineQueueLength(callback: (response: ApiResponse<number>) => void) {
        callback({
            status: 'error',
            error: 'Numeric value has exceeded Number.MAX_SAFE_INTEGER.'
        });
    }
};

// 각 프로퍼티는 promisify<T>(oldApi.request...),

promisify 함수는 oldApi의 각 메서드를 인자로 받는다.

function promisify<T>(fn: (callback: (response: ApiResponse<T>) => void) => void){}

oldApi 객체의 메서드 callback 인자 타입을 promisify 함수의 인자 타입에 동일하게 선언을 해줬다.

function promisify<T>(fn: (callback: (response: ApiResponse<T>) => void) => void): () => Promise<T> {
    return () => new Promise<T>((resolve, reject) => {
        const realCallback = (response: ApiResponse<T>) => {
            if (response.status === 'success') {
                resolve(response.data);
            } else {
                reject(new Error(response.error));
            }
        };

        fn(realCallback);
    });
}

// 간단하게 쓰면 이렇게도 써줄 수 있다.
 return () => new Promise((resolve, reject) => {
        fn((response: ApiResponse<T>) => {
            if(response.status === 'success') {
                resolve(response.data);
            } else {
                reject(new Error(response.error));
            }

        });
    })

// response.status가 success인 경우에는 resolve
// oldApi 메서드의 callback에 인자가 {status: '... ', data: ... }이기 때문에 response.data, response.status 이런식으로 사용할 수 있는 것임

new Promise resolve / reject 하는 함수를 반환하도록 한다.

() => newPromise()

이런 식으로 작성하는 이유는...?

const a = () => {
  return () => console.log('333');
}

const b = a();
// b를 console에 출력해보면 () => console.log('333')이 출력된다.

console.log(b());
// b를 호출해야 '333'이 출력된다.

const api = {
    requestAdmins: promisify<Admin[]>(oldApi.requestAdmins),
}
// promisify 함수를 호출하고 있음.
// 이 부분은 예시로 생각하면 b의 역할을 하고 있는 것이다.
// promisify 함수가 a의 역할이라고 볼 수 있다.

async function startTheApp() {
    console.log('Admins:');
    (await api.requestAdmins()).forEach(logPerson);
    console.log();
}
// api.requestAdmins를 호출하고 있음.
// 이 부분은 b를 호출한 것이라고 볼 수 있다.

타입스크립트를 하면서도 콘솔을 찍어보는 습관을 들여야 되겠다는 생각을 했다. 그냥 자바스크립트에서 타입스크립트로 바뀐 것 뿐인데, 콘솔을 안 찍게 된다...(왜 이러는 건지?)

그리고 왜 타입스크립트 exercise 10을 풀 수 있어야 비로소 이력서에 작성할 수 있다고 말했는지 알겠다. 엄청 어려웠다.
답을 보고, 다른 사람이 작성한 것도 찾아보고, 물어보고 했기 때문에 겨우 해결할 수 있었다. 이제 10번 문제의 심화 과정까지 풀어보고, 포스팅 해봐야 되겠다.

(현재 10번 문제의 심화문제 풀어보고 있는데, 정말 어렵다. 포스팅 할 수는 있겠지...?)

profile
경험한 것을 기록

0개의 댓글