Typescript Exercise 10
Exercise 10
We don’t want to reimplement all the data-requesting functions. Let’s decorate the old callback-based functions with the new Promise-compatible result.
The final function should return a Promise which would resolve with the final data directly (i.e. users or admins) or would reject with an error (or type Error). The function should be named promisify.
Higher difficulty bonus exercise:
Create a function promisifyAll which accepts an object
- with functions and returns a new object where each of the function is promisified.
// Rewrite api creation accordingly:
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.`
);
}
);
How I Solved This Problem
First, We need to clarify what function promisify
does
What function promisify does actually
In the exercise it says about the function promisify
function should be promisicated when it is facing callback hell
It can be done by simply converting function to Promise function
The document explains it like below
“Promisification” is a long word for a simple transformation. It’s the conversion of a function that accepts a callback into a function that returns a promise.
In this exercise we need to follow the requirement
The final function should return a Promise which would resolve with the final data directly
which function promisify
needs to return resolved data which will be either fulfilled
or rejected
Let's make a new type that returns
Promise using generic type
type PromisicateddResult<T> = () => Promise<T>;
export function promisify(callbackFn: unknown): PromisicateddResult<T> {
// this function should return Promise
return () =>
new Promise((resolve, reject) => {
callbackFn((response) => {
if (response.status === 'success') {
resolve(response.data);
} else {
reject(new Error(response.error));
}
});
});
}
But, the passed parameter callbackFn is still needs to be specified
export type ApiResponse<T> =
| {
status: 'success';
data: T;
}
| {
status: 'error';
error: string;
};
type CallbackBasedAsyncFunction<T> = (
callback: (response: ApiResponse<T>) => void
) => void;
type PromisicateddResult<T> = () => Promise<T>;
export function promisify<T>(
callbackFn: CallbackBasedAsyncFunction<T>
): PromisicateddResult<T> {
// this function should return Promise
return () =>
new Promise((resolve, reject) => {
callbackFn((response) => {
if (response.status === 'success') {
resolve(response.data);
} else {
reject(new Error(response.error));
}
});
});
}