From dfa99c602c797998ef14d21f5384ee34491a73b7 Mon Sep 17 00:00:00 2001 From: xuan hoang Date: Tue, 23 Jan 2024 00:52:20 +0700 Subject: [PATCH 1/3] ex_hoanglx ex1~ex6 --- ex1_hoanglx/ex1.ts | 108 ++++++++++++++++++++++++++++++++++++++++++ ex1_hoanglx/ex2.ts | 65 ++++++++++++++++++++++++++ ex1_hoanglx/ex3.ts | 69 +++++++++++++++++++++++++++ ex1_hoanglx/ex4.ts | 69 +++++++++++++++++++++++++++ ex1_hoanglx/ex5.ts | 114 +++++++++++++++++++++++++++++++++++++++++++++ ex1_hoanglx/ex6.ts | 84 +++++++++++++++++++++++++++++++++ 6 files changed, 509 insertions(+) create mode 100644 ex1_hoanglx/ex1.ts create mode 100644 ex1_hoanglx/ex2.ts create mode 100644 ex1_hoanglx/ex3.ts create mode 100644 ex1_hoanglx/ex4.ts create mode 100644 ex1_hoanglx/ex5.ts create mode 100644 ex1_hoanglx/ex6.ts diff --git a/ex1_hoanglx/ex1.ts b/ex1_hoanglx/ex1.ts new file mode 100644 index 0000000..1b9b101 --- /dev/null +++ b/ex1_hoanglx/ex1.ts @@ -0,0 +1,108 @@ +/* + +Welcome to: + + ................................................................ + . . + . #################### #################### E . + . #################### #################### X . + . #### #### E . + . #### #### R . + . #### #################### C . + . #### #### I . + . #### #### S . + . #### #################### E . + . #### #################### S . + . . + ................................................................ + + The goal: Let everyone play with many different TypeScript features + and get an overview of TypeScript capabilities and principles. + + Things to cover: + + 1. Basic typing. + 2. Refining types. + 3. Union types. + 4. Merged types. + 5. Generics. + 6. Type declarations. + 7. Module augmentation. + 8. Advanced type mapping. + + Rules and principles: + + 1. Avoid using "any" type at all costs. + 2. Difficulty quickly grows one exercise after another. + 3. Feel free to send pull requests if you've come up + with improvements! + 4. Provide feedback to the creator of these exercises. + 5. Enjoy. + +Brief UI guide: + + +--------------------------------------------------------------+ + | TypeScript exercises | + +--------------------------------------------------------------+ + | Exercises 1·2·3·4... << Navigate through exercises >> | + +---------------+----------------------------------------------+ + | Files | file.ts << Filename and status >> | + +---------------+----------------------------------------------+ + | file.ts | 1 import {x} from 'y'; | + | dir | 2 | + | sub.ts | 3 | + | | | + | << Current | << Currently selected file code editor >> | + | exercise file | | + | structure >> +----------------------------------------------+ + | | | + | | << Errors to fix in order to proceed >> | + | | | + +---------------+----------------------------------------------+ + +Intro: + + We are starting a small community of users. For performance + reasons, we have decided to store all users right in the code. + This way we can provide our developers with more + user-interaction opportunities. With user-related data, at least. + All the GDPR-related issues will be solved some other day. + This would be the basis for our future experiments during + these exercises. + +Exercise: + + Given the data, define the interface "User" and use it accordingly. + +*/ + + +interface User { + name: string; + age: number; + occupation: string; +}; + +export const users: User[] = [ + { + name: 'Max Mustermann', + age: 25, + occupation: 'Chimney sweep' + }, + { + name: 'Kate Müller', + age: 23, + occupation: 'Astronaut' + } +]; + +export function logPerson(user: User) { + console.log(` - ${user.name}, ${user.age}`); +} + +console.log('Users:'); +users.forEach(logPerson); + + +// In case you are stuck: +// https://www.typescriptlang.org/docs/handbook/2/objects.html diff --git a/ex1_hoanglx/ex2.ts b/ex1_hoanglx/ex2.ts new file mode 100644 index 0000000..a53ab9e --- /dev/null +++ b/ex1_hoanglx/ex2.ts @@ -0,0 +1,65 @@ +/* + +Intro: + + All 2 users liked the idea of the community. We should go + forward and introduce some order. We are in Germany after all. + Let's add a couple of admins. + + Initially, we only had users in the in-memory database. After + introducing Admins, we need to fix the types so that + everything works well together. + +Exercise: + + Type "Person" is missing, please define it and use + it in persons array and logPerson function in order to fix + all the TS errors. + +*/ + +interface User { + name: string; + age: number; + occupation: string; +} + +interface Admin { + name: string; + age: number; + role: string; +} + +export type Person = User | Admin; + +export const persons: Person[] /* <- Person[] */ = [ + { + name: 'Max Mustermann', + age: 25, + occupation: 'Chimney sweep' + }, + { + name: 'Jane Doe', + age: 32, + role: 'Administrator' + }, + { + name: 'Kate Müller', + age: 23, + occupation: 'Astronaut' + }, + { + name: 'Bruce Willis', + age: 64, + role: 'World saver' + } +]; + +export function logPerson(user: Person) { + console.log(` - ${user.name}, ${user.age}`); +} + +persons.forEach(logPerson); + +// In case you are stuck: +// https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#union-types diff --git a/ex1_hoanglx/ex3.ts b/ex1_hoanglx/ex3.ts new file mode 100644 index 0000000..09076a3 --- /dev/null +++ b/ex1_hoanglx/ex3.ts @@ -0,0 +1,69 @@ +/* + +Intro: + + Since we already have some of the additional + information about our users, it's a good idea + to output it in a nice way. + +Exercise: + + Fix type errors in logPerson function. + + logPerson function should accept both User and Admin + and should output relevant information according to + the input: occupation for User and role for Admin. + +*/ + +interface User { + name: string; + age: number; + occupation: string; +} + +interface Admin { + name: string; + age: number; + role: string; +} + +export type Person = User | Admin; + +export const persons: Person[] = [ + { + name: 'Max Mustermann', + age: 25, + occupation: 'Chimney sweep' + }, + { + name: 'Jane Doe', + age: 32, + role: 'Administrator' + }, + { + name: 'Kate Müller', + age: 23, + occupation: 'Astronaut' + }, + { + name: 'Bruce Willis', + age: 64, + role: 'World saver' + } +]; + +export function logPerson(person: Person) { + let additionalInformation: string; + if ("role" in person) { + additionalInformation = person.role; + } else { + additionalInformation = person.occupation; + } + console.log(` - ${person.name}, ${person.age}, ${additionalInformation}`); +} + +persons.forEach(logPerson); + +// In case you are stuck: +// https://www.typescriptlang.org/docs/handbook/2/narrowing.html#the-in-operator-narrowing diff --git a/ex1_hoanglx/ex4.ts b/ex1_hoanglx/ex4.ts new file mode 100644 index 0000000..4eb79c0 --- /dev/null +++ b/ex1_hoanglx/ex4.ts @@ -0,0 +1,69 @@ +/* + +Intro: + + As we introduced "type" to both User and Admin + it's now easier to distinguish between them. + Once object type checking logic was extracted + into separate functions isUser and isAdmin - + logPerson function got new type errors. + +Exercise: + + Figure out how to help TypeScript understand types in + this situation and apply necessary fixes. + +*/ + +interface User { + type: 'user'; + name: string; + age: number; + occupation: string; +} + +interface Admin { + type: 'admin'; + name: string; + age: number; + role: string; +} + +export type Person = User | Admin; + +export const persons: Person[] = [ + { type: 'user', name: 'Max Mustermann', age: 25, occupation: 'Chimney sweep' }, + { type: 'admin', name: 'Jane Doe', age: 32, role: 'Administrator' }, + { type: 'user', name: 'Kate Müller', age: 23, occupation: 'Astronaut' }, + { type: 'admin', name: 'Bruce Willis', age: 64, role: 'World saver' } +]; + +export function isAdmin(person: Person) : person is Admin { + return person.type === 'admin'; +} + +export function isUser(person: Person) : person is User { + return person.type === 'user'; +} + +export function logPerson(person: Person) { + let additionalInformation: string = ''; + if (isAdmin(person) ) { + additionalInformation = person.role; + } + if (isUser(person) ) { + additionalInformation = person.occupation; + } + console.log(` - ${person.name}, ${person.age}, ${additionalInformation}`); +} + +console.log('Admins:'); +persons.filter(isAdmin).forEach(logPerson); + +console.log(); + +console.log('Users:'); +persons.filter(isUser).forEach(logPerson); + +// In case you are stuck: +// https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates diff --git a/ex1_hoanglx/ex5.ts b/ex1_hoanglx/ex5.ts new file mode 100644 index 0000000..7f00885 --- /dev/null +++ b/ex1_hoanglx/ex5.ts @@ -0,0 +1,114 @@ +/* + +Intro: + + Time to filter the data! In order to be flexible + we filter users using a number of criteria and + return only those matching all of the criteria. + We don't need Admins yet, we only filter Users. + +Exercise: + + Without duplicating type structures, modify + filterUsers function definition so that we can + pass only those criteria which are needed, + and not the whole User information as it is + required now according to typing. + +Higher difficulty bonus exercise: + + Exclude "type" from filter criteria. + +*/ + +interface User { + type: 'user'; + name: string; + age: number; + occupation: string; +} + +interface Admin { + type: 'admin'; + name: string; + age: number; + role: string; +} + +export type Person = User | Admin; + +export const persons: Person[] = [ + { + type: 'user', + name: 'Max Mustermann', + age: 25, + occupation: 'Chimney sweep' + }, + { + type: 'admin', + name: 'Jane Doe', + age: 32, + role: 'Administrator' + }, + { + type: 'user', + name: 'Kate Müller', + age: 23, + occupation: 'Astronaut' + }, + { + type: 'admin', + name: 'Bruce Willis', + age: 64, + role: 'World saver' + }, + { + type: 'user', + name: 'Wilson', + age: 23, + occupation: 'Ball' + }, + { + type: 'admin', + name: 'Agent Smith', + age: 23, + role: 'Administrator' + } +]; + + +export const isAdmin = (person: Person): person is Admin => person.type === 'admin'; +export const isUser = (person: Person): person is User => person.type === 'user'; + +export function logPerson(person: Person) { + let additionalInformation = ''; + if (isAdmin(person)) { + additionalInformation = person.role; + } + if (isUser(person)) { + additionalInformation = person.occupation; + } + console.log(` - ${person.name}, ${person.age}, ${additionalInformation}`); +} + +export function filterUsers(persons: Person[], criteria: Omit): User[] { + return persons.filter(isUser).filter((user) => { + const criteriaKeys = Object.keys(criteria) as (keyof User)[]; + return criteriaKeys.every((fieldName) => { + return user[fieldName] === criteria[fieldName]; + }); + }); +} + +console.log('Users of age 23:'); + +filterUsers( + persons, + { + age: 23 + } +).forEach(logPerson); + +// In case you are stuck: +// https://www.typescriptlang.org/docs/handbook/utility-types.html#partialtype +// https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html#predefined-conditional-types diff --git a/ex1_hoanglx/ex6.ts b/ex1_hoanglx/ex6.ts new file mode 100644 index 0000000..e7a91bb --- /dev/null +++ b/ex1_hoanglx/ex6.ts @@ -0,0 +1,84 @@ +/* + +Intro: + + Filtering requirements have grown. We need to be + able to filter any kind of Persons. + +Exercise: + + Fix typing for the filterPersons so that it can filter users + and return User[] when personType='user' and return Admin[] + when personType='admin'. Also filterPersons should accept + partial User/Admin type according to the personType. + `criteria` argument should behave according to the + `personType` argument value. `type` field is not allowed in + the `criteria` field. + +Higher difficulty bonus exercise: + + Implement a function `getObjectKeys()` which returns more + convenient result for any argument given, so that you don't + need to cast it. + + let criteriaKeys = Object.keys(criteria) as (keyof User)[]; + --> + let criteriaKeys = getObjectKeys(criteria); + +*/ + +interface User { + type: 'user'; + name: string; + age: number; + occupation: string; +} + +interface Admin { + type: 'admin'; + name: string; + age: number; + role: string; +} + +export type Person = User | Admin; + +export const persons: Person[] = [ + { type: 'user', name: 'Max Mustermann', age: 25, occupation: 'Chimney sweep' }, + { type: 'admin', name: 'Jane Doe', age: 32, role: 'Administrator' }, + { type: 'user', name: 'Kate Müller', age: 23, occupation: 'Astronaut' }, + { type: 'admin', name: 'Bruce Willis', age: 64, role: 'World saver' }, + { type: 'user', name: 'Wilson', age: 23, occupation: 'Ball' }, + { type: 'admin', name: 'Agent Smith', age: 23, role: 'Anti-virus engineer' } +]; + +export function logPerson(person: Person) { + console.log( + ` - ${person.name}, ${person.age}, ${person.type === 'admin' ? person.role : person.occupation}` + ); +} + +export function filterPersons(persons: Person[], personType: string, criteria: Omit): Person[] { + return persons + .filter((person) => person.type === personType) + .filter((person) => { + let criteriaKeys = Object.keys(criteria) as (keyof Person)[]; + return criteriaKeys.every((fieldName) => { + return person[fieldName] === (criteria)[fieldName]; + }); + }); +} + +export const usersOfAge23 = filterPersons(persons, 'user', { age: 23 }); +export const adminsOfAge23 = filterPersons(persons, 'admin', { age: 23 }); + +console.log('Users of age 23:'); +usersOfAge23.forEach(logPerson); + +console.log(); + +console.log('Admins of age 23:'); +adminsOfAge23.forEach(logPerson); + +// In case you are stuck: +// https://www.typescriptlang.org/docs/handbook/2/functions.html#function-overloads From 8abca60b2e2ce2807141e48929edcddb3132763b Mon Sep 17 00:00:00 2001 From: xuan hoang Date: Thu, 25 Jan 2024 00:49:00 +0700 Subject: [PATCH 2/3] Ex7 ~ Ex9 --- ex1_hoanglx/ex7.ts | 124 ++++++++++++++++++++++++++++++++++++ ex1_hoanglx/ex8.ts | 95 ++++++++++++++++++++++++++++ ex1_hoanglx/ex9.ts | 153 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 372 insertions(+) create mode 100644 ex1_hoanglx/ex7.ts create mode 100644 ex1_hoanglx/ex8.ts create mode 100644 ex1_hoanglx/ex9.ts diff --git a/ex1_hoanglx/ex7.ts b/ex1_hoanglx/ex7.ts new file mode 100644 index 0000000..8b5a7ed --- /dev/null +++ b/ex1_hoanglx/ex7.ts @@ -0,0 +1,124 @@ +/* + +Intro: + + Filtering was completely removed from the project. + It turned out that this feature was just not needed + for the end-user and we spent a lot of time just because + our office manager told us to do so. Next time we should + instead listen to the product management. + + Anyway we have a new plan. CEO's friend Nick told us + that if we randomly swap user names from time to time + in the community, it would be very funny and the project + would definitely succeed! + +Exercise: + + Implement swap which receives 2 persons and returns them in + the reverse order. The function itself is already + there, actually. We just need to provide it with proper types. + Also this function shouldn't necessarily be limited to just + Person types, lets type it so that it works with any two types + specified. + +*/ + +interface User { + type: 'user'; + name: string; + age: number; + occupation: string; +} + +interface Admin { + type: 'admin'; + name: string; + age: number; + role: string; +} + +function logUser(user: User) { + const pos = users.indexOf(user) + 1; + console.log(` - #${pos} User: ${user.name}, ${user.age}, ${user.occupation}`); +} + +function logAdmin(admin: Admin) { + const pos = admins.indexOf(admin) + 1; + console.log(` - #${pos} Admin: ${admin.name}, ${admin.age}, ${admin.role}`); +} + +const admins: Admin[] = [ + { + type: 'admin', + name: 'Will Bruces', + age: 30, + role: 'Overseer' + }, + { + type: 'admin', + name: 'Steve', + age: 40, + role: 'Steve' + } +]; + +const users: User[] = [ + { + type: 'user', + name: 'Moses', + age: 70, + occupation: 'Desert guide' + }, + { + type: 'user', + name: 'Superman', + age: 28, + occupation: 'Ordinary person' + } +]; + +export function swap(v1: T1, v2: T2): [T2, T1] { + return [v2, v1]; +} + +function test1() { + console.log('test1:'); + const [secondUser, firstAdmin] = swap(admins[0], users[1]); + logUser(secondUser); + logAdmin(firstAdmin); +} + +function test2() { + console.log('test2:'); + const [secondAdmin, firstUser] = swap(users[0], admins[1]); + logAdmin(secondAdmin); + logUser(firstUser); +} + +function test3() { + console.log('test3:'); + const [secondUser, firstUser] = swap(users[0], users[1]); + logUser(secondUser); + logUser(firstUser); +} + +function test4() { + console.log('test4:'); + const [firstAdmin, secondAdmin] = swap(admins[1], admins[0]); + logAdmin(firstAdmin); + logAdmin(secondAdmin); +} + +function test5() { + console.log('test5:'); + const [stringValue, numericValue] = swap(123, 'Hello World'); + console.log(` - String: ${stringValue}`); + console.log(` - Numeric: ${numericValue}`); +} + +[test1, test2, test3, test4, test5].forEach((test) => test()); + +// In case you are stuck: +// https://www.typescriptlang.org/docs/handbook/2/objects.html#tuple-types +// https://www.typescriptlang.org/docs/handbook/2/generics.html diff --git a/ex1_hoanglx/ex8.ts b/ex1_hoanglx/ex8.ts new file mode 100644 index 0000000..eb4c65a --- /dev/null +++ b/ex1_hoanglx/ex8.ts @@ -0,0 +1,95 @@ +/* + +Intro: + + Project grew and we ended up in a situation with + some users starting to have more influence. + Therefore, we decided to create a new person type + called PowerUser which is supposed to combine + everything User and Admin have. + +Exercise: + + Define type PowerUser which should have all fields + from both User and Admin (except for type), + and also have type 'powerUser' without duplicating + all the fields in the code. + +*/ + +interface User { + type: 'user'; + name: string; + age: number; + occupation: string; +} + +interface Admin { + type: 'admin'; + name: string; + age: number; + role: string; +} + +type PowerUser = Omit & Omit & { + type: 'powerUser' +}; + +export type Person = User | Admin | PowerUser; + +export const persons: Person[] = [ + { type: 'user', name: 'Max Mustermann', age: 25, occupation: 'Chimney sweep' }, + { type: 'admin', name: 'Jane Doe', age: 32, role: 'Administrator' }, + { type: 'user', name: 'Kate Müller', age: 23, occupation: 'Astronaut' }, + { type: 'admin', name: 'Bruce Willis', age: 64, role: 'World saver' }, + { + type: 'powerUser', + name: 'Nikki Stone', + age: 45, + role: 'Moderator', + occupation: 'Cat groomer' + } +]; + +function isAdmin(person: Person): person is Admin { + return person.type === 'admin'; +} + +function isUser(person: Person): person is User { + return person.type === 'user'; +} + +function isPowerUser(person: Person): person is PowerUser { + return person.type === 'powerUser'; +} + +export function logPerson(person: Person) { + let additionalInformation: string = ''; + if (isAdmin(person)) { + additionalInformation = person.role; + } + if (isUser(person)) { + additionalInformation = person.occupation; + } + if (isPowerUser(person)) { + additionalInformation = `${person.role}, ${person.occupation}`; + } + console.log(`${person.name}, ${person.age}, ${additionalInformation}`); +} + +console.log('Admins:'); +persons.filter(isAdmin).forEach(logPerson); + +console.log(); + +console.log('Users:'); +persons.filter(isUser).forEach(logPerson); + +console.log(); + +console.log('Power users:'); +persons.filter(isPowerUser).forEach(logPerson); + +// In case you are stuck: +// https://www.typescriptlang.org/docs/handbook/utility-types.html +// https://www.typescriptlang.org/docs/handbook/2/objects.html#intersection-types diff --git a/ex1_hoanglx/ex9.ts b/ex1_hoanglx/ex9.ts new file mode 100644 index 0000000..287cda0 --- /dev/null +++ b/ex1_hoanglx/ex9.ts @@ -0,0 +1,153 @@ +/* + +Intro: + + PowerUsers idea was bad. Once those users got + extended permissions, they started bullying others + and we lost a lot of great users. + As a response we spent all the remaining money + on the marketing and got even more users. + We need to start preparing to move everything to a + real database. For now we just do some mocks. + + The server API format was decided to be the following: + + In case of success: { status: 'success', data: RESPONSE_DATA } + In case of error: { status: 'error', error: ERROR_MESSAGE } + + The API engineer started creating types for this API and + quickly figured out that the amount of types needed to be + created is too big. + +Exercise: + + Remove UsersApiResponse and AdminsApiResponse types + and use generic type ApiResponse in order to specify API + response formats for each of the functions. + +*/ + +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 = +{ status: 'success'; data: T } +| { status: 'error'; error: string }; +; + + +export function requestAdmins(callback: (response: ApiResponse) => void) { + callback({ + status: 'success', + data: admins + }); +} + + +export function requestUsers(callback: (response: ApiResponse) => void) { + callback({ + status: 'success', + data: users + }); +} + +export function requestCurrentServerTime(callback: (response: ApiResponse) => void) { + callback({ + status: 'success', + data: Date.now() + }); +} + +export function requestCoffeeMachineQueueLength(callback: (response: ApiResponse) => void) { + callback({ + status: 'error', + error: 'Numeric value has exceeded Number.MAX_SAFE_INTEGER.' + }); +} + +function logPerson(person: Person) { + console.log( + ` - ${person.name}, ${person.age}, ${person.type === 'admin' ? person.role : person.occupation}` + ); +} + +function startTheApp(callback: (error: Error | null) => void) { + requestAdmins((adminsResponse) => { + console.log('Admins:'); + if (adminsResponse.status === 'success') { + adminsResponse.data.forEach(logPerson); + } else { + return callback(new Error(adminsResponse.error)); + } + + console.log(); + + requestUsers((usersResponse) => { + console.log('Users:'); + if (usersResponse.status === 'success') { + usersResponse.data.forEach(logPerson); + } else { + return callback(new Error(usersResponse.error)); + } + + console.log(); + + requestCurrentServerTime((serverTimeResponse) => { + console.log('Server time:'); + if (serverTimeResponse.status === 'success') { + console.log(` ${new Date(serverTimeResponse.data).toLocaleString()}`); + } else { + return callback(new Error(serverTimeResponse.error)); + } + + console.log(); + + requestCoffeeMachineQueueLength((coffeeMachineQueueLengthResponse) => { + console.log('Coffee machine queue length:'); + if (coffeeMachineQueueLengthResponse.status === 'success') { + console.log(` ${coffeeMachineQueueLengthResponse.data}`); + } else { + return callback(new Error(coffeeMachineQueueLengthResponse.error)); + } + + callback(null); + }); + }); + }); + }); +} + +startTheApp((e: Error | null) => { + console.log(); + if (e) { + console.log(`Error: "${e.message}", but it's fine, sometimes errors are inevitable.`) + } else { + console.log('Success!'); + } +}); + +// In case you are stuck: +// https://www.typescriptlang.org/docs/handbook/2/generics.html From f282c69dde1e76b2fe29016bfba4e80dd9a48043 Mon Sep 17 00:00:00 2001 From: xuan hoang Date: Wed, 31 Jan 2024 00:40:37 +0700 Subject: [PATCH 3/3] fix err test case --- ex1_hoanglx/ex10.ts | 155 ++++++++++++++++++++++++++++++++++++++++++++ ex1_hoanglx/ex5.ts | 14 ++-- ex1_hoanglx/ex6.ts | 13 ++-- 3 files changed, 171 insertions(+), 11 deletions(-) create mode 100644 ex1_hoanglx/ex10.ts diff --git a/ex1_hoanglx/ex10.ts b/ex1_hoanglx/ex10.ts new file mode 100644 index 0000000..0c1af43 --- /dev/null +++ b/ex1_hoanglx/ex10.ts @@ -0,0 +1,155 @@ +/* + +Intro: + + We have asynchronous functions now, advanced technology. + This makes us a tech startup officially now. + But one of the consultants spoiled our dreams about + inevitable future IT leadership. + He said that callback-based asynchronicity is not + popular anymore and everyone should use Promises. + He promised that if we switch to Promises, this would + bring promising results. + +Exercise: + + 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 = ( + { + status: 'success'; + data: T; + } | + { + status: 'error'; + error: string; + } +); + +export function promisify(callbackFunction: (callback: (response: ApiResponse) => void) => void): () => Promise { + return () => { + return new Promise((resolve, reject) => { + callbackFunction((response) => { + if (response.status === 'success') { + resolve(response.data); + } else { + reject(new Error(response.error)); + } + }); + }); + }; +} +const oldApi = { + requestAdmins(callback: (response: ApiResponse) => void) { + callback({ + status: 'success', + data: admins + }); + }, + requestUsers(callback: (response: ApiResponse) => void) { + callback({ + status: 'success', + data: users + }); + }, + requestCurrentServerTime(callback: (response: ApiResponse) => void) { + callback({ + status: 'success', + data: Date.now() + }); + }, + requestCoffeeMachineQueueLength(callback: (response: ApiResponse) => 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.`); + } +); + +// In case you are stuck: +// https://www.typescriptlang.org/docs/handbook/2/generics.html diff --git a/ex1_hoanglx/ex5.ts b/ex1_hoanglx/ex5.ts index 7f00885..1009f8b 100644 --- a/ex1_hoanglx/ex5.ts +++ b/ex1_hoanglx/ex5.ts @@ -91,15 +91,19 @@ export function logPerson(person: Person) { console.log(` - ${person.name}, ${person.age}, ${additionalInformation}`); } -export function filterUsers(persons: Person[], criteria: Omit): User[] { +export function filterUsers(persons: Person[], criteria: Partial>): User[] { return persons.filter(isUser).filter((user) => { - const criteriaKeys = Object.keys(criteria) as (keyof User)[]; - return criteriaKeys.every((fieldName) => { - return user[fieldName] === criteria[fieldName]; - }); + const criteriaKeys = Object.keys(criteria) as (keyof User)[]; + return criteriaKeys.every((fieldName) => { + if (fieldName === 'type') { + return user[fieldName] === 'user'; + } + return user[fieldName] === criteria[fieldName]; + }); }); } + console.log('Users of age 23:'); filterUsers( diff --git a/ex1_hoanglx/ex6.ts b/ex1_hoanglx/ex6.ts index e7a91bb..d89bd44 100644 --- a/ex1_hoanglx/ex6.ts +++ b/ex1_hoanglx/ex6.ts @@ -57,15 +57,16 @@ export function logPerson(person: Person) { ` - ${person.name}, ${person.age}, ${person.type === 'admin' ? person.role : person.occupation}` ); } - -export function filterPersons(persons: Person[], personType: string, criteria: Omit): Person[] { +export function filterPersons(persons: Person[], personType: User['type'], criteria: Partial>): User[]; +export function filterPersons(persons: Person[], personType: Admin['type'], criteria: Partial>): Admin[]; +export function filterPersons(persons: Person[], personType: Person['type'], criteria: Partial): Person[] { return persons .filter((person) => person.type === personType) .filter((person) => { - let criteriaKeys = Object.keys(criteria) as (keyof Person)[]; - return criteriaKeys.every((fieldName) => { - return person[fieldName] === (criteria)[fieldName]; - }); + let criteriaKeys = Object.keys(criteria) as (keyof Person)[]; + return criteriaKeys.every((fieldName) => { + return person[fieldName] === criteria[fieldName]; + }); }); }