├── .browserslistrc ├── .gitignore ├── .vscode └── settings.json ├── 02-get-started ├── README.md ├── begin │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierrc.js │ ├── .vscode │ │ └── settings.json │ ├── README.md │ ├── db.json │ ├── index.html │ ├── package-lock.json │ ├── package.json │ ├── routes.json │ ├── src │ │ ├── design │ │ │ ├── _bulma.scss │ │ │ ├── _styles.scss │ │ │ ├── _variables.scss │ │ │ └── index.scss │ │ ├── index.ts │ │ └── lib │ │ │ ├── data.ts │ │ │ ├── dom.ts │ │ │ ├── index.ts │ │ │ └── interfaces.ts │ ├── tsconfig.json │ └── webpack.config.js └── end │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierrc.js │ ├── .vscode │ └── settings.json │ ├── README.md │ ├── db.json │ ├── index.html │ ├── package-lock.json │ ├── package.json │ ├── routes.json │ ├── src │ ├── design │ │ ├── _bulma.scss │ │ ├── _styles.scss │ │ ├── _variables.scss │ │ └── index.scss │ ├── index.ts │ └── lib │ │ ├── data.ts │ │ ├── dom.ts │ │ ├── index.ts │ │ └── interfaces.ts │ ├── tsconfig.json │ └── webpack.config.js ├── 03-identify-async ├── README.md ├── begin │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierrc.js │ ├── .vscode │ │ └── settings.json │ ├── README.md │ ├── baking.ts │ ├── db.json │ ├── index.html │ ├── package-lock.json │ ├── package.json │ ├── routes.json │ ├── src │ │ ├── design │ │ │ ├── _bulma.scss │ │ │ ├── _styles.scss │ │ │ ├── _variables.scss │ │ │ └── index.scss │ │ ├── heroes.component.ts │ │ ├── index.ts │ │ └── lib │ │ │ ├── data.ts │ │ │ ├── dom.ts │ │ │ ├── index.ts │ │ │ ├── interfaces.ts │ │ │ ├── modal.ts │ │ │ └── timer.ts │ ├── tsconfig.json │ └── webpack.config.js └── end │ ├── README.md │ └── package-lock.json ├── 04-callback ├── README.md ├── begin │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierrc.js │ ├── .vscode │ │ └── settings.json │ ├── README.md │ ├── db.json │ ├── index.html │ ├── package-lock.json │ ├── package.json │ ├── routes.json │ ├── src │ │ ├── design │ │ │ ├── _bulma.scss │ │ │ ├── _styles.scss │ │ │ ├── _variables.scss │ │ │ └── index.scss │ │ ├── examples │ │ │ ├── baking.ts │ │ │ ├── get-ingredients.ts │ │ │ ├── ingredients.ts │ │ │ └── sync-and-async.ts │ │ ├── heroes.component.ts │ │ ├── index.ts │ │ └── lib │ │ │ ├── data │ │ │ ├── callback.ts │ │ │ ├── config.ts │ │ │ └── index.ts │ │ │ ├── dom.ts │ │ │ ├── index.ts │ │ │ ├── interfaces.ts │ │ │ ├── modal.ts │ │ │ └── timer.ts │ ├── tsconfig.json │ └── webpack.config.js └── end │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierrc.js │ ├── .vscode │ └── settings.json │ ├── README.md │ ├── db.json │ ├── index.html │ ├── package-lock.json │ ├── package.json │ ├── routes.json │ ├── src │ ├── design │ │ ├── _bulma.scss │ │ ├── _styles.scss │ │ ├── _variables.scss │ │ └── index.scss │ ├── examples │ │ ├── baking.ts │ │ ├── get-ingredients.ts │ │ ├── ingredients.ts │ │ └── sync-and-async.ts │ ├── heroes.component.ts │ ├── index.ts │ └── lib │ │ ├── data │ │ ├── callback.ts │ │ ├── config.ts │ │ └── index.ts │ │ ├── dom.ts │ │ ├── index.ts │ │ ├── interfaces.ts │ │ ├── modal.ts │ │ └── timer.ts │ ├── tsconfig.json │ └── webpack.config.js ├── 05-promises ├── README.md ├── begin │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierrc.js │ ├── .vscode │ │ └── settings.json │ ├── README.md │ ├── compare.md │ ├── db.json │ ├── index.html │ ├── package-lock.json │ ├── package.json │ ├── routes.json │ ├── src │ │ ├── design │ │ │ ├── _bulma.scss │ │ │ ├── _styles.scss │ │ │ ├── _variables.scss │ │ │ └── index.scss │ │ ├── examples │ │ │ ├── heroes.ts │ │ │ └── promise.ts │ │ ├── heroes.component.ts │ │ ├── index.ts │ │ └── lib │ │ │ ├── data │ │ │ ├── callback.ts │ │ │ ├── config.ts │ │ │ ├── index.ts │ │ │ └── promise.ts │ │ │ ├── dom.ts │ │ │ ├── index.ts │ │ │ ├── interfaces.ts │ │ │ ├── modal.ts │ │ │ └── timer.ts │ ├── tsconfig.json │ └── webpack.config.js └── end │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierrc.js │ ├── .vscode │ └── settings.json │ ├── README.md │ ├── compare.md │ ├── db.json │ ├── index.html │ ├── package-lock.json │ ├── package.json │ ├── routes.json │ ├── src │ ├── design │ │ ├── _bulma.scss │ │ ├── _styles.scss │ │ ├── _variables.scss │ │ └── index.scss │ ├── examples │ │ ├── heroes.ts │ │ └── promise.ts │ ├── heroes.component.ts │ ├── index.ts │ └── lib │ │ ├── data │ │ ├── callback.ts │ │ ├── config.ts │ │ ├── index.ts │ │ └── promise.ts │ │ ├── dom.ts │ │ ├── index.ts │ │ ├── interfaces.ts │ │ ├── modal.ts │ │ └── timer.ts │ ├── tsconfig.json │ └── webpack.config.js ├── 06-async-await ├── README.md ├── begin │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierrc.js │ ├── .vscode │ │ └── settings.json │ ├── README copy.md │ ├── README.md │ ├── compare.md │ ├── db.json │ ├── index.html │ ├── package-lock.json │ ├── package.json │ ├── routes.json │ ├── src │ │ ├── design │ │ │ ├── _bulma.scss │ │ │ ├── _styles.scss │ │ │ ├── _variables.scss │ │ │ └── index.scss │ │ ├── examples │ │ │ ├── await.ts │ │ │ └── heroes.ts │ │ ├── heroes.component.ts │ │ ├── index.ts │ │ └── lib │ │ │ ├── data │ │ │ ├── await.ts │ │ │ ├── callback.ts │ │ │ ├── config.ts │ │ │ ├── index.ts │ │ │ └── promise.ts │ │ │ ├── dom.ts │ │ │ ├── index.ts │ │ │ ├── interfaces.ts │ │ │ ├── modal.ts │ │ │ └── timer.ts │ ├── tsconfig.json │ └── webpack.config.js └── end │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierrc.js │ ├── .vscode │ └── settings.json │ ├── README.md │ ├── compare.md │ ├── db.json │ ├── index.html │ ├── package-lock.json │ ├── package.json │ ├── routes.json │ ├── src │ ├── design │ │ ├── _bulma.scss │ │ ├── _styles.scss │ │ ├── _variables.scss │ │ └── index.scss │ ├── examples │ │ ├── await.ts │ │ └── heroes.ts │ ├── heroes.component.ts │ ├── index.ts │ └── lib │ │ ├── data │ │ ├── await.ts │ │ ├── callback.ts │ │ ├── config.ts │ │ ├── index.ts │ │ └── promise.ts │ │ ├── dom.ts │ │ ├── index.ts │ │ ├── interfaces.ts │ │ ├── modal.ts │ │ └── timer.ts │ ├── tsconfig.json │ └── webpack.config.js ├── README.md ├── package-lock.json ├── package.json └── xx-final ├── .eslintrc.js ├── .gitignore ├── .prettierrc.js ├── .vscode └── settings.json ├── README.md ├── compare.md ├── db.json ├── index.html ├── package-lock.json ├── package.json ├── routes.json ├── src ├── design │ ├── _bulma.scss │ ├── _styles.scss │ ├── _variables.scss │ └── index.scss ├── heroes.component.ts ├── index.ts └── lib │ ├── data │ ├── await.ts │ ├── callback.ts │ ├── config.ts │ ├── index.ts │ └── promise.ts │ ├── dom.ts │ ├── index.ts │ ├── interfaces.ts │ ├── modal.ts │ └── timer.ts ├── tsconfig.json └── webpack.config.js /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | /node_modules 10 | node_modules 11 | 12 | # IDEs and editors 13 | /.idea 14 | .project 15 | .classpath 16 | .c9/ 17 | *.launch 18 | .settings/ 19 | *.sublime-workspace 20 | 21 | # IDE - VSCode 22 | .vscode/* 23 | !.vscode/settings.json 24 | !.vscode/tasks.json 25 | !.vscode/launch.json 26 | !.vscode/extensions.json 27 | 28 | # misc 29 | /.sass-cache 30 | /connect.lock 31 | /coverage 32 | /libpeerconnection.log 33 | npm-debug.log 34 | yarn-error.log 35 | testem.log 36 | /typings 37 | 38 | # System Files 39 | .DS_Store 40 | Thumbs.db 41 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "workbench.colorCustomizations": { 3 | "activityBar.background": "#f9e64f", 4 | "activityBar.activeBorder": "#059a89", 5 | "activityBar.foreground": "#15202b", 6 | "activityBar.inactiveForeground": "#15202b99", 7 | "activityBarBadge.background": "#059a89", 8 | "activityBarBadge.foreground": "#e7e7e7", 9 | "titleBar.activeBackground": "#f9e64f", 10 | "titleBar.inactiveBackground": "#f9e64f99", 11 | "titleBar.activeForeground": "#15202b", 12 | "titleBar.inactiveForeground": "#15202b99", 13 | "statusBar.background": "#f9e64f", 14 | "statusBarItem.hoverBackground": "#f7df1e", 15 | "statusBar.foreground": "#15202b", 16 | "activityBar.activeBackground": "#f9e64f", 17 | "sash.hoverBorder": "#f9e64f", 18 | "statusBarItem.remoteBackground": "#f9e64f", 19 | "statusBarItem.remoteForeground": "#15202b" 20 | }, 21 | "peacock.color": "#f9e64f", 22 | "typescript.tsdk": "node_modules/typescript/lib" 23 | } 24 | -------------------------------------------------------------------------------- /02-get-started/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnpapa/typescript-async/bd7bfd359844248efa2b834b438f53dc36aec393/02-get-started/README.md -------------------------------------------------------------------------------- /02-get-started/begin/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: false, 5 | }, 6 | parser: '@typescript-eslint/parser', // Specifies the ESLint parser 7 | extends: [ 8 | // 'prettier', 9 | // Uses the recommended rules from the @typescript-eslint/eslint-plugin 10 | 'plugin:@typescript-eslint/recommended', 11 | // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier 12 | 'prettier/@typescript-eslint', 13 | // Enables eslint-plugin-prettier and displays prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array. 14 | 'plugin:prettier/recommended', 15 | ], 16 | plugins: ['@typescript-eslint', 'prettier'], 17 | // watch this for explaining why some of this is here 18 | // https://www.youtube.com/watch?time_continue=239&v=YIvjKId9m2c 19 | rules: { 20 | // 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 21 | 'no-console': 'off', 22 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', 23 | '@typescript-eslint/explicit-function-return-type': 'off', 24 | '@typescript-eslint/no-use-before-define': 'off', 25 | quotes: [2, 'single', { avoidEscape: true, allowTemplateLiterals: true }], 26 | 'prettier/prettier': [ 27 | 'error', 28 | { 29 | // trailingComma: 'all', 30 | // singleQuote: true, 31 | // printWidth: 80, 32 | }, 33 | ], 34 | }, 35 | }; 36 | -------------------------------------------------------------------------------- /02-get-started/begin/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | /node_modules 10 | node_modules 11 | 12 | # IDEs and editors 13 | /.idea 14 | .project 15 | .classpath 16 | .c9/ 17 | *.launch 18 | .settings/ 19 | *.sublime-workspace 20 | 21 | # IDE - VSCode 22 | .vscode/* 23 | !.vscode/settings.json 24 | !.vscode/tasks.json 25 | !.vscode/launch.json 26 | !.vscode/extensions.json 27 | 28 | # misc 29 | /.sass-cache 30 | /connect.lock 31 | /coverage 32 | /libpeerconnection.log 33 | npm-debug.log 34 | yarn-error.log 35 | testem.log 36 | /typings 37 | 38 | # System Files 39 | .DS_Store 40 | Thumbs.db 41 | -------------------------------------------------------------------------------- /02-get-started/begin/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 80, 3 | tabWidth: 2, 4 | bracketSpacing: true, 5 | singleQuote: true, 6 | trailingComma: 'all', 7 | }; 8 | -------------------------------------------------------------------------------- /02-get-started/begin/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.enable": true, 3 | "workbench.colorCustomizations": { 4 | "activityBar.background": "#fbed80", 5 | "activityBar.activeBorder": "#06b9a5", 6 | "activityBar.foreground": "#15202b", 7 | "activityBar.inactiveForeground": "#15202b99", 8 | "activityBarBadge.background": "#06b9a5", 9 | "activityBarBadge.foreground": "#15202b", 10 | "titleBar.activeBackground": "#f9e64f", 11 | "titleBar.inactiveBackground": "#f9e64f99", 12 | "titleBar.activeForeground": "#15202b", 13 | "titleBar.inactiveForeground": "#15202b99", 14 | "statusBar.background": "#f9e64f", 15 | "statusBarItem.hoverBackground": "#f7df1e", 16 | "statusBar.foreground": "#15202b", 17 | "panel.border": "#fbed80", 18 | "sideBar.border": "#fbed80", 19 | "editorGroup.border": "#fbed80", 20 | "tab.activeBorder": "#fbed80" 21 | }, 22 | "peacock.color": "#f9e64f", 23 | "typescript.tsdk": "node_modules/typescript/lib" 24 | } -------------------------------------------------------------------------------- /02-get-started/begin/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnpapa/typescript-async/bd7bfd359844248efa2b834b438f53dc36aec393/02-get-started/begin/README.md -------------------------------------------------------------------------------- /02-get-started/begin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "end", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "keywords": [], 7 | "author": "", 8 | "license": "ISC", 9 | "scripts": { 10 | "dev": "concurrently \"npm run backend\" \"webpack-dev-server\"", 11 | "build": "webpack --mode production", 12 | "backend": "json-server --watch db.json --routes routes.json --port 8081 --middlewares ./node_modules/json-server-reset --delay 500" 13 | }, 14 | "dependencies": { 15 | "@fortawesome/fontawesome-svg-core": "^1.2.18", 16 | "@fortawesome/free-solid-svg-icons": "^5.8.2", 17 | "axios": "^0.19.0" 18 | }, 19 | "devDependencies": { 20 | "@typescript-eslint/eslint-plugin": "^2.3.2", 21 | "@typescript-eslint/parser": "^2.3.2", 22 | "bulma": "^0.9.3", 23 | "concurrently": "^4.1.2", 24 | "copy-webpack-plugin": "^6.4.1", 25 | "css-loader": "^3.2.0", 26 | "eslint": "^6.7.0", 27 | "eslint-config-prettier": "^6.7.0", 28 | "eslint-plugin-import": "^2.18.2", 29 | "eslint-plugin-prettier": "^3.1.1", 30 | "extract-text-webpack-plugin": "^4.0.0-beta.0", 31 | "json-server": "^0.15.1", 32 | "json-server-reset": "^1.2.0", 33 | "mini-css-extract-plugin": "^0.8.0", 34 | "sass": "^1.44.0", 35 | "prettier": "^1.19.1", 36 | "sass-loader": "^8.0.0", 37 | "style-loader": "^1.0.0", 38 | "ts-loader": "^6.2.0", 39 | "typescript": "^3.7.0-beta", 40 | "webpack": "^4.41.0", 41 | "webpack-cli": "^3.3.9", 42 | "webpack-dev-server": "^3.8.2" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /02-get-started/begin/routes.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/*": "/$1", 3 | "/hero": "/heroes", 4 | "/heroes/:email": "/heroes?email=:email", 5 | "/order": "/orders", 6 | "/orders/:heroId": "/orders?heroId=:heroId", 7 | "/accountreps/:heroId": "/accountreps?heroId=:heroId", 8 | "/shippingstatuses/:orderNum": "/shippingstatuses?orderNum=:orderNum", 9 | "/reset": "/reset" 10 | } 11 | -------------------------------------------------------------------------------- /02-get-started/begin/src/design/_bulma.scss: -------------------------------------------------------------------------------- 1 | @import 'bulma/bulma.sass'; 2 | 3 | // Using specific Bulma files reduces the 3 production css files from 195kb to 139kb for a 28% decrease 4 | // @import 'bulma/sass/utilities/_all.sass'; 5 | // @import 'bulma/sass/utilities/mixins.sass'; 6 | // // @import 'bulma/sass/utilities/controls.sass'; 7 | // // @import 'bulma/sass/base/generic.sass'; 8 | // // @import 'bulma/sass/base/helpers.sass'; // 277 lines 9 | // // @import 'bulma/sass/base/minireset.sass'; 10 | // // @import 'bulma/sass/layout/footer.sass'; 11 | // // @import 'bulma/sass/layout/section.sass'; // 12 lines 12 | // @import 'bulma/sass/components/card.sass'; 13 | // @import 'bulma/sass/components/list.sass'; // 39 lines 14 | // @import 'bulma/sass/components/menu.sass'; 15 | // // @import 'bulma/sass/components/modal.sass'; 16 | // @import 'bulma/sass/components/navbar.sass'; 17 | // // @import 'bulma/sass/elements/notification.sass'; 18 | // @import 'bulma/sass/elements/button.sass'; 19 | // @import 'bulma/sass/elements/container.sass'; // 25 lines 20 | // @import 'bulma/sass/elements/content.sass'; // 150 lines 21 | // @import 'bulma/sass/elements/title.sass'; // 64 lines 22 | // // // @import 'bulma/sass/elements/icon.sass'; 23 | // // @import 'bulma/sass/form/_all.sass'; 24 | // @import 'bulma/sass/grid/columns.sass'; 25 | -------------------------------------------------------------------------------- /02-get-started/begin/src/design/_variables.scss: -------------------------------------------------------------------------------- 1 | $javascript: #f9e64f; 2 | $javascript-light: #fdf190; 3 | $typescript: #4386dd; //294e80 4 | $typescript-light: #4180d3; 5 | $primary: $typescript; 6 | $primary-light: $typescript-light; 7 | $link: $primary; 8 | $info: $primary; 9 | $shade-light: #fafafa; 10 | $accent-color: #00b3e6; 11 | $accent-dark-color: #294e80; 12 | $table-background-color: #b84949; //$shade-light; 13 | -------------------------------------------------------------------------------- /02-get-started/begin/src/design/index.scss: -------------------------------------------------------------------------------- 1 | // @charset "utf-8"; 2 | // @import '~bulma/bulma'; 3 | @import 'variables'; 4 | @import 'bulma'; 5 | @import 'styles'; 6 | 7 | // Import a Google Font 8 | @import url('https://fonts.googleapis.com/css?family=Nunito:400,700'); 9 | @import url('https://use.fontawesome.com/releases/v5.4.1/css/all.css'); 10 | -------------------------------------------------------------------------------- /02-get-started/begin/src/index.ts: -------------------------------------------------------------------------------- 1 | import './design/index.scss'; 2 | import { showMessage } from './lib'; 3 | 4 | document.querySelector('#bake-cookies').addEventListener('click', async () => { 5 | bake(); 6 | }); 7 | 8 | async function bake() { 9 | const title = 'Baking cookies'; 10 | let counter = 0; 11 | console.clear(); 12 | console.group(title); 13 | 14 | counter++; 15 | console.log(`${counter} - Add ingredients`); 16 | showMessage(`${counter} - Add ingredients`, title); 17 | 18 | counter++; 19 | console.log(`${counter} - Mix ingredients`); 20 | showMessage(`${counter} - Mix ingredients`, title, true); 21 | 22 | counter++; 23 | console.log(`${counter} - Bake at 325 degrees for 10 minutes`); 24 | showMessage(`${counter} - Bake at 325 degrees for 10 minutes`, title, true); 25 | 26 | counter++; 27 | console.log(`${counter} - Eat cookies`); 28 | showMessage(`${counter} - Eat cookies`, title, true); 29 | 30 | console.groupEnd(); 31 | } 32 | -------------------------------------------------------------------------------- /02-get-started/begin/src/lib/data.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | import { Hero } from './interfaces'; 4 | 5 | export const getHeroAsync = async function(email: string) { 6 | try { 7 | const response = await axios.get(`api/heroes?email=${email.toLowerCase()}`); 8 | const { data } = response; 9 | const hero: Hero = data[0]; 10 | return hero; 11 | } catch (error) { 12 | console.error(`Developer Error: Async Data Error: ${error.message}`); 13 | throw new Error(`Oh no! We're unable to fetch the Hero`); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /02-get-started/begin/src/lib/dom.ts: -------------------------------------------------------------------------------- 1 | export function setText( 2 | el: DocumentFragment | HTMLElement, 3 | selector: string, 4 | text: string, 5 | ) { 6 | el.querySelector(selector).textContent = text.toString(); 7 | return el; 8 | } 9 | 10 | export function getText(el: DocumentFragment | HTMLElement, selector: string) { 11 | return el.querySelector(selector).textContent; 12 | } 13 | 14 | export const createDiv = (...classList: string[]) => { 15 | const el = document.createElement('div'); 16 | el.classList.add(...classList); 17 | return el; 18 | }; 19 | 20 | export function cloneElementsFromTemplate(templateName: string) { 21 | const template = document.getElementById(templateName) as HTMLTemplateElement; 22 | const clone = document.importNode(template.content, true); 23 | return clone; 24 | } 25 | 26 | export function showMessage(text = '', title = 'Info', append = false) { 27 | const el = document.getElementById('message-box'); 28 | el.style.visibility = !!text ? 'visible' : 'hidden'; 29 | 30 | let newText = text; 31 | if (append) { 32 | let oldText = getText(el, '.message-body'); 33 | newText = `${oldText}\r\n${text}`; 34 | } 35 | 36 | setText(el, '.message-header', title); 37 | setText(el, '.message-body', newText); 38 | } 39 | 40 | export function showFetching(selector: string) { 41 | const progressClone = cloneElementsFromTemplate('progress-template'); 42 | const heroPlaceholder = document.querySelector(selector); 43 | heroPlaceholder.replaceWith(progressClone); 44 | } 45 | -------------------------------------------------------------------------------- /02-get-started/begin/src/lib/index.ts: -------------------------------------------------------------------------------- 1 | export * from './data'; 2 | export * from './dom'; 3 | export * from './interfaces'; 4 | -------------------------------------------------------------------------------- /02-get-started/begin/src/lib/interfaces.ts: -------------------------------------------------------------------------------- 1 | export interface Hero { 2 | id: number; 3 | name: string; 4 | description: string; 5 | email: string; 6 | orders?: Order[]; 7 | accountRep?: AccountRepresentative; 8 | } 9 | 10 | export interface Order { 11 | heroId: number; 12 | num: number; 13 | items: Item[]; 14 | shippingStatus: ShippingStatus; 15 | } 16 | 17 | export interface AccountRepresentative { 18 | repId: number; 19 | name: string; 20 | } 21 | 22 | export interface ShippingStatus { 23 | [index: number]: ShippingStatus; 24 | orderNum: number; 25 | status: string; 26 | } 27 | 28 | export interface Item { 29 | orderNum: number; 30 | name: string; 31 | qty: number; 32 | price: number; 33 | } 34 | 35 | export interface Callback { 36 | (data: T): void; 37 | } 38 | 39 | export interface CallbackError { 40 | (msg?: string): void; 41 | } 42 | -------------------------------------------------------------------------------- /02-get-started/begin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist/", 4 | "sourceMap": true, 5 | "noImplicitAny": true, 6 | // "module": "es6", 7 | "module": "commonjs", 8 | "target": "es5", 9 | // "allowJs": true, 10 | "allowSyntheticDefaultImports": true, 11 | "lib": ["es2018", "dom"] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /02-get-started/begin/webpack.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const path = require('path'); 3 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 4 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 5 | 6 | const index = 'index'; 7 | 8 | module.exports = { 9 | entry: `./src/${index}.ts`, 10 | devtool: 'inline-source-map', 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.tsx?$/, 15 | use: 'ts-loader', 16 | exclude: /node_modules/, 17 | }, 18 | { 19 | test: /\.s[ac]ss$/i, 20 | use: [ 21 | MiniCssExtractPlugin.loader, 22 | { 23 | loader: 'css-loader', 24 | }, 25 | { 26 | loader: 'sass-loader', 27 | options: { 28 | sourceMap: true, 29 | }, 30 | }, 31 | ], 32 | }, 33 | ], 34 | }, 35 | plugins: [ 36 | new MiniCssExtractPlugin({ 37 | filename: '[name].bundle.css', 38 | }), 39 | new CopyWebpackPlugin({ 40 | patterns: [ 41 | { 42 | from: `./${index}.html`, 43 | }, 44 | ], 45 | }), 46 | ], 47 | resolve: { 48 | extensions: ['.ts', '.js'], 49 | }, 50 | output: { 51 | filename: 'bundle.js', 52 | path: path.resolve(__dirname, 'dist'), 53 | }, 54 | performance: { 55 | maxEntrypointSize: 640000, 56 | maxAssetSize: 640000, 57 | }, 58 | devServer: { 59 | contentBase: 'dist', 60 | port: 8080, 61 | // Send API requests on localhost to API server get around CORS. 62 | proxy: { 63 | '/api': { 64 | target: { 65 | host: '0.0.0.0', 66 | protocol: 'http:', 67 | port: 8081, 68 | }, 69 | pathRewrite: { 70 | '^/api': '', 71 | }, 72 | }, 73 | }, 74 | }, 75 | }; 76 | -------------------------------------------------------------------------------- /02-get-started/end/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: false, 5 | }, 6 | parser: '@typescript-eslint/parser', // Specifies the ESLint parser 7 | extends: [ 8 | // 'prettier', 9 | // Uses the recommended rules from the @typescript-eslint/eslint-plugin 10 | 'plugin:@typescript-eslint/recommended', 11 | // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier 12 | 'prettier/@typescript-eslint', 13 | // Enables eslint-plugin-prettier and displays prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array. 14 | 'plugin:prettier/recommended', 15 | ], 16 | plugins: ['@typescript-eslint', 'prettier'], 17 | // watch this for explaining why some of this is here 18 | // https://www.youtube.com/watch?time_continue=239&v=YIvjKId9m2c 19 | rules: { 20 | // 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 21 | 'no-console': 'off', 22 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', 23 | '@typescript-eslint/explicit-function-return-type': 'off', 24 | '@typescript-eslint/no-use-before-define': 'off', 25 | quotes: [2, 'single', { avoidEscape: true, allowTemplateLiterals: true }], 26 | 'prettier/prettier': [ 27 | 'error', 28 | { 29 | // trailingComma: 'all', 30 | // singleQuote: true, 31 | // printWidth: 80, 32 | }, 33 | ], 34 | }, 35 | }; 36 | -------------------------------------------------------------------------------- /02-get-started/end/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | /node_modules 10 | node_modules 11 | 12 | # IDEs and editors 13 | /.idea 14 | .project 15 | .classpath 16 | .c9/ 17 | *.launch 18 | .settings/ 19 | *.sublime-workspace 20 | 21 | # IDE - VSCode 22 | .vscode/* 23 | !.vscode/settings.json 24 | !.vscode/tasks.json 25 | !.vscode/launch.json 26 | !.vscode/extensions.json 27 | 28 | # misc 29 | /.sass-cache 30 | /connect.lock 31 | /coverage 32 | /libpeerconnection.log 33 | npm-debug.log 34 | yarn-error.log 35 | testem.log 36 | /typings 37 | 38 | # System Files 39 | .DS_Store 40 | Thumbs.db 41 | -------------------------------------------------------------------------------- /02-get-started/end/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 80, 3 | tabWidth: 2, 4 | bracketSpacing: true, 5 | singleQuote: true, 6 | trailingComma: 'all', 7 | }; 8 | -------------------------------------------------------------------------------- /02-get-started/end/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.enable": true, 3 | "workbench.colorCustomizations": { 4 | "activityBar.background": "#fbed80", 5 | "activityBar.activeBorder": "#06b9a5", 6 | "activityBar.foreground": "#15202b", 7 | "activityBar.inactiveForeground": "#15202b99", 8 | "activityBarBadge.background": "#06b9a5", 9 | "activityBarBadge.foreground": "#15202b", 10 | "titleBar.activeBackground": "#f9e64f", 11 | "titleBar.inactiveBackground": "#f9e64f99", 12 | "titleBar.activeForeground": "#15202b", 13 | "titleBar.inactiveForeground": "#15202b99", 14 | "statusBar.background": "#f9e64f", 15 | "statusBarItem.hoverBackground": "#f7df1e", 16 | "statusBar.foreground": "#15202b", 17 | "panel.border": "#fbed80", 18 | "sideBar.border": "#fbed80", 19 | "editorGroup.border": "#fbed80", 20 | "tab.activeBorder": "#fbed80" 21 | }, 22 | "peacock.color": "#f9e64f", 23 | "typescript.tsdk": "node_modules/typescript/lib" 24 | } -------------------------------------------------------------------------------- /02-get-started/end/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnpapa/typescript-async/bd7bfd359844248efa2b834b438f53dc36aec393/02-get-started/end/README.md -------------------------------------------------------------------------------- /02-get-started/end/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "end", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "keywords": [], 7 | "author": "", 8 | "license": "ISC", 9 | "scripts": { 10 | "dev": "concurrently \"npm run backend\" \"webpack-dev-server\"", 11 | "build": "webpack --mode production", 12 | "backend": "json-server --watch db.json --routes routes.json --port 8081 --middlewares ./node_modules/json-server-reset --delay 500" 13 | }, 14 | "dependencies": { 15 | "@fortawesome/fontawesome-svg-core": "^1.2.18", 16 | "@fortawesome/free-solid-svg-icons": "^5.8.2", 17 | "axios": "^0.19.0" 18 | }, 19 | "devDependencies": { 20 | "@typescript-eslint/eslint-plugin": "^2.3.2", 21 | "@typescript-eslint/parser": "^2.3.2", 22 | "bulma": "^0.9.3", 23 | "concurrently": "^4.1.2", 24 | "copy-webpack-plugin": "^6.4.1", 25 | "css-loader": "^3.2.0", 26 | "eslint": "^6.7.0", 27 | "eslint-config-prettier": "^6.7.0", 28 | "eslint-plugin-import": "^2.18.2", 29 | "eslint-plugin-prettier": "^3.1.1", 30 | "extract-text-webpack-plugin": "^4.0.0-beta.0", 31 | "json-server": "^0.15.1", 32 | "json-server-reset": "^1.2.0", 33 | "mini-css-extract-plugin": "^0.8.0", 34 | "sass": "^1.44.0", 35 | "prettier": "^1.19.1", 36 | "sass-loader": "^8.0.0", 37 | "style-loader": "^1.0.0", 38 | "ts-loader": "^6.2.0", 39 | "typescript": "^3.7.0-beta", 40 | "webpack": "^4.41.0", 41 | "webpack-cli": "^3.3.9", 42 | "webpack-dev-server": "^3.8.2" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /02-get-started/end/routes.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/*": "/$1", 3 | "/hero": "/heroes", 4 | "/heroes/:email": "/heroes?email=:email", 5 | "/order": "/orders", 6 | "/orders/:heroId": "/orders?heroId=:heroId", 7 | "/accountreps/:heroId": "/accountreps?heroId=:heroId", 8 | "/shippingstatuses/:orderNum": "/shippingstatuses?orderNum=:orderNum", 9 | "/reset": "/reset" 10 | } 11 | -------------------------------------------------------------------------------- /02-get-started/end/src/design/_bulma.scss: -------------------------------------------------------------------------------- 1 | @import 'bulma/bulma.sass'; 2 | 3 | // Using specific Bulma files reduces the 3 production css files from 195kb to 139kb for a 28% decrease 4 | // @import 'bulma/sass/utilities/_all.sass'; 5 | // @import 'bulma/sass/utilities/mixins.sass'; 6 | // // @import 'bulma/sass/utilities/controls.sass'; 7 | // // @import 'bulma/sass/base/generic.sass'; 8 | // // @import 'bulma/sass/base/helpers.sass'; // 277 lines 9 | // // @import 'bulma/sass/base/minireset.sass'; 10 | // // @import 'bulma/sass/layout/footer.sass'; 11 | // // @import 'bulma/sass/layout/section.sass'; // 12 lines 12 | // @import 'bulma/sass/components/card.sass'; 13 | // @import 'bulma/sass/components/list.sass'; // 39 lines 14 | // @import 'bulma/sass/components/menu.sass'; 15 | // // @import 'bulma/sass/components/modal.sass'; 16 | // @import 'bulma/sass/components/navbar.sass'; 17 | // // @import 'bulma/sass/elements/notification.sass'; 18 | // @import 'bulma/sass/elements/button.sass'; 19 | // @import 'bulma/sass/elements/container.sass'; // 25 lines 20 | // @import 'bulma/sass/elements/content.sass'; // 150 lines 21 | // @import 'bulma/sass/elements/title.sass'; // 64 lines 22 | // // // @import 'bulma/sass/elements/icon.sass'; 23 | // // @import 'bulma/sass/form/_all.sass'; 24 | // @import 'bulma/sass/grid/columns.sass'; 25 | -------------------------------------------------------------------------------- /02-get-started/end/src/design/_variables.scss: -------------------------------------------------------------------------------- 1 | $javascript: #f9e64f; 2 | $javascript-light: #fdf190; 3 | $typescript: #4386dd; //294e80 4 | $typescript-light: #4180d3; 5 | $primary: $typescript; 6 | $primary-light: $typescript-light; 7 | $link: $primary; 8 | $info: $primary; 9 | $shade-light: #fafafa; 10 | $accent-color: #00b3e6; 11 | $accent-dark-color: #294e80; 12 | $table-background-color: #b84949; //$shade-light; 13 | -------------------------------------------------------------------------------- /02-get-started/end/src/design/index.scss: -------------------------------------------------------------------------------- 1 | // @charset "utf-8"; 2 | // @import '~bulma/bulma'; 3 | @import 'variables'; 4 | @import 'bulma'; 5 | @import 'styles'; 6 | 7 | // Import a Google Font 8 | @import url('https://fonts.googleapis.com/css?family=Nunito:400,700'); 9 | @import url('https://use.fontawesome.com/releases/v5.4.1/css/all.css'); 10 | -------------------------------------------------------------------------------- /02-get-started/end/src/index.ts: -------------------------------------------------------------------------------- 1 | import './design/index.scss'; 2 | import { showMessage } from './lib'; 3 | 4 | document.querySelector('#bake-cookies').addEventListener('click', async () => { 5 | bake(); 6 | }); 7 | 8 | async function bake() { 9 | const title = 'Baking cookies'; 10 | let counter = 0; 11 | console.clear(); 12 | console.group(title); 13 | 14 | counter++; 15 | console.log(`${counter} - Add ingredients`); 16 | showMessage(`${counter} - Add ingredients`, title); 17 | 18 | counter++; 19 | console.log(`${counter} - Mix ingredients`); 20 | showMessage(`${counter} - Mix ingredients`, title, true); 21 | 22 | setTimeout(() => { 23 | counter++; 24 | console.log(`${counter} - Bake at 325 degrees for 10 minutes`); 25 | showMessage(`${counter} - Bake at 325 degrees for 10 minutes`, title, true); 26 | }, 1000); 27 | 28 | counter++; 29 | console.log(`${counter} - Eat cookies`); 30 | showMessage(`${counter} - Eat cookies`, title, true); 31 | 32 | console.groupEnd(); 33 | } 34 | -------------------------------------------------------------------------------- /02-get-started/end/src/lib/data.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | import { Hero } from './interfaces'; 4 | 5 | export const getHeroAsync = async function(email: string) { 6 | try { 7 | const response = await axios.get(`api/heroes?email=${email.toLowerCase()}`); 8 | const { data } = response; 9 | const hero: Hero = data[0]; 10 | return hero; 11 | } catch (error) { 12 | console.error(`Developer Error: Async Data Error: ${error.message}`); 13 | throw new Error(`Oh no! We're unable to fetch the Hero`); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /02-get-started/end/src/lib/dom.ts: -------------------------------------------------------------------------------- 1 | export function setText( 2 | el: DocumentFragment | HTMLElement, 3 | selector: string, 4 | text: string, 5 | ) { 6 | el.querySelector(selector).textContent = text.toString(); 7 | return el; 8 | } 9 | 10 | export function getText(el: DocumentFragment | HTMLElement, selector: string) { 11 | return el.querySelector(selector).textContent; 12 | } 13 | 14 | export const createDiv = (...classList: string[]) => { 15 | const el = document.createElement('div'); 16 | el.classList.add(...classList); 17 | return el; 18 | }; 19 | 20 | export function cloneElementsFromTemplate(templateName: string) { 21 | const template = document.getElementById(templateName) as HTMLTemplateElement; 22 | const clone = document.importNode(template.content, true); 23 | return clone; 24 | } 25 | 26 | export function showMessage(text = '', title = 'Info', append = false) { 27 | const el = document.getElementById('message-box'); 28 | el.style.visibility = !!text ? 'visible' : 'hidden'; 29 | 30 | let newText = text; 31 | if (append) { 32 | let oldText = getText(el, '.message-body'); 33 | newText = `${oldText}\r\n${text}`; 34 | } 35 | 36 | setText(el, '.message-header', title); 37 | setText(el, '.message-body', newText); 38 | } 39 | 40 | export function showFetching(selector: string) { 41 | const progressClone = cloneElementsFromTemplate('progress-template'); 42 | const heroPlaceholder = document.querySelector(selector); 43 | heroPlaceholder.replaceWith(progressClone); 44 | } 45 | -------------------------------------------------------------------------------- /02-get-started/end/src/lib/index.ts: -------------------------------------------------------------------------------- 1 | export * from './data'; 2 | export * from './dom'; 3 | export * from './interfaces'; 4 | -------------------------------------------------------------------------------- /02-get-started/end/src/lib/interfaces.ts: -------------------------------------------------------------------------------- 1 | export interface Hero { 2 | id: number; 3 | name: string; 4 | description: string; 5 | email: string; 6 | orders?: Order[]; 7 | accountRep?: AccountRepresentative; 8 | } 9 | 10 | export interface Order { 11 | heroId: number; 12 | num: number; 13 | items: Item[]; 14 | shippingStatus: ShippingStatus; 15 | } 16 | 17 | export interface AccountRepresentative { 18 | repId: number; 19 | name: string; 20 | } 21 | 22 | export interface ShippingStatus { 23 | [index: number]: ShippingStatus; 24 | orderNum: number; 25 | status: string; 26 | } 27 | 28 | export interface Item { 29 | orderNum: number; 30 | name: string; 31 | qty: number; 32 | price: number; 33 | } 34 | 35 | export interface Callback { 36 | (data: T): void; 37 | } 38 | 39 | export interface CallbackError { 40 | (msg?: string): void; 41 | } 42 | -------------------------------------------------------------------------------- /02-get-started/end/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist/", 4 | "sourceMap": true, 5 | "noImplicitAny": true, 6 | // "module": "es6", 7 | "module": "commonjs", 8 | "target": "es5", 9 | // "allowJs": true, 10 | "allowSyntheticDefaultImports": true, 11 | "lib": ["es2018", "dom"] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /02-get-started/end/webpack.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const path = require('path'); 3 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 4 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 5 | 6 | const index = 'index'; 7 | 8 | module.exports = { 9 | entry: `./src/${index}.ts`, 10 | devtool: 'inline-source-map', 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.tsx?$/, 15 | use: 'ts-loader', 16 | exclude: /node_modules/, 17 | }, 18 | { 19 | test: /\.s[ac]ss$/i, 20 | use: [ 21 | MiniCssExtractPlugin.loader, 22 | { 23 | loader: 'css-loader', 24 | }, 25 | { 26 | loader: 'sass-loader', 27 | options: { 28 | sourceMap: true, 29 | }, 30 | }, 31 | ], 32 | }, 33 | ], 34 | }, 35 | plugins: [ 36 | new MiniCssExtractPlugin({ 37 | filename: '[name].bundle.css', 38 | }), 39 | new CopyWebpackPlugin({ 40 | patterns: [ 41 | { 42 | from: `./${index}.html`, 43 | }, 44 | ], 45 | }), 46 | ], 47 | resolve: { 48 | extensions: ['.ts', '.js'], 49 | }, 50 | output: { 51 | filename: 'bundle.js', 52 | path: path.resolve(__dirname, 'dist'), 53 | }, 54 | performance: { 55 | maxEntrypointSize: 640000, 56 | maxAssetSize: 640000, 57 | }, 58 | devServer: { 59 | contentBase: 'dist', 60 | port: 8080, 61 | // Send API requests on localhost to API server get around CORS. 62 | proxy: { 63 | '/api': { 64 | target: { 65 | host: '0.0.0.0', 66 | protocol: 'http:', 67 | port: 8081, 68 | }, 69 | pathRewrite: { 70 | '^/api': '', 71 | }, 72 | }, 73 | }, 74 | }, 75 | }; 76 | -------------------------------------------------------------------------------- /03-identify-async/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnpapa/typescript-async/bd7bfd359844248efa2b834b438f53dc36aec393/03-identify-async/README.md -------------------------------------------------------------------------------- /03-identify-async/begin/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: false, 5 | }, 6 | parser: '@typescript-eslint/parser', // Specifies the ESLint parser 7 | extends: [ 8 | // 'prettier', 9 | // Uses the recommended rules from the @typescript-eslint/eslint-plugin 10 | 'plugin:@typescript-eslint/recommended', 11 | // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier 12 | 'prettier/@typescript-eslint', 13 | // Enables eslint-plugin-prettier and displays prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array. 14 | 'plugin:prettier/recommended', 15 | ], 16 | plugins: ['@typescript-eslint', 'prettier'], 17 | // watch this for explaining why some of this is here 18 | // https://www.youtube.com/watch?time_continue=239&v=YIvjKId9m2c 19 | rules: { 20 | // 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 21 | 'no-console': 'off', 22 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', 23 | '@typescript-eslint/explicit-function-return-type': 'off', 24 | '@typescript-eslint/no-use-before-define': 'off', 25 | quotes: [2, 'single', { avoidEscape: true, allowTemplateLiterals: true }], 26 | 'prettier/prettier': [ 27 | 'error', 28 | { 29 | // trailingComma: 'all', 30 | // singleQuote: true, 31 | // printWidth: 80, 32 | }, 33 | ], 34 | }, 35 | }; 36 | -------------------------------------------------------------------------------- /03-identify-async/begin/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | /node_modules 10 | node_modules 11 | 12 | # IDEs and editors 13 | /.idea 14 | .project 15 | .classpath 16 | .c9/ 17 | *.launch 18 | .settings/ 19 | *.sublime-workspace 20 | 21 | # IDE - VSCode 22 | .vscode/* 23 | !.vscode/settings.json 24 | !.vscode/tasks.json 25 | !.vscode/launch.json 26 | !.vscode/extensions.json 27 | 28 | # misc 29 | /.sass-cache 30 | /connect.lock 31 | /coverage 32 | /libpeerconnection.log 33 | npm-debug.log 34 | yarn-error.log 35 | testem.log 36 | /typings 37 | 38 | # System Files 39 | .DS_Store 40 | Thumbs.db 41 | -------------------------------------------------------------------------------- /03-identify-async/begin/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 80, 3 | tabWidth: 2, 4 | bracketSpacing: true, 5 | singleQuote: true, 6 | trailingComma: 'all', 7 | }; 8 | -------------------------------------------------------------------------------- /03-identify-async/begin/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.enable": true, 3 | "workbench.colorCustomizations": { 4 | "activityBar.background": "#fbed80", 5 | "activityBar.activeBorder": "#06b9a5", 6 | "activityBar.foreground": "#15202b", 7 | "activityBar.inactiveForeground": "#15202b99", 8 | "activityBarBadge.background": "#06b9a5", 9 | "activityBarBadge.foreground": "#15202b", 10 | "titleBar.activeBackground": "#f9e64f", 11 | "titleBar.inactiveBackground": "#f9e64f99", 12 | "titleBar.activeForeground": "#15202b", 13 | "titleBar.inactiveForeground": "#15202b99", 14 | "statusBar.background": "#f9e64f", 15 | "statusBarItem.hoverBackground": "#f7df1e", 16 | "statusBar.foreground": "#15202b", 17 | "panel.border": "#fbed80", 18 | "sideBar.border": "#fbed80", 19 | "editorGroup.border": "#fbed80", 20 | "tab.activeBorder": "#fbed80" 21 | }, 22 | "peacock.color": "#f9e64f", 23 | "typescript.tsdk": "node_modules/typescript/lib" 24 | } 25 | -------------------------------------------------------------------------------- /03-identify-async/begin/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnpapa/typescript-async/bd7bfd359844248efa2b834b438f53dc36aec393/03-identify-async/begin/README.md -------------------------------------------------------------------------------- /03-identify-async/begin/baking.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | /* eslint-disable @typescript-eslint/no-explicit-any */ 3 | /** 4 | * Baking Cookies 5 | * 6 | * This code does not run. 7 | * It is here as an example 8 | * of a mixed sync and async flow. 9 | */ 10 | const ingredients = [ 11 | '2 1/4 cups all-purpose flour', 12 | '1 teaspoon baking soda', 13 | '1 teaspoon salt', 14 | '1 cup (2 sticks) butter', 15 | '3/4 cup granulated sugar', 16 | '3/4 cup packed brown sugar', 17 | '1 teaspoon vanilla extract', 18 | '2 large eggs', 19 | '2 cups (12-oz. pkg.) chocolate chips', 20 | ]; 21 | 22 | export function bakeCookies() { 23 | const bowl = combine(ingredients); 24 | 25 | const batter = mix(bowl); 26 | 27 | const cookieSheet = { batter, temp: 375, minutes: 10 }; 28 | 29 | bake(cookieSheet, cookies => { 30 | /** 31 | * we must wait 10 minutes for the oven 32 | * to bake the cookies 33 | * before we eat 34 | **/ 35 | eat(cookies); 36 | }); 37 | 38 | /** 39 | * private functions are below 40 | * 41 | **/ 42 | 43 | function combine(i: any) { 44 | // Logic to combine the ingredients 45 | const bowl = { 46 | /* combined indgredients */ 47 | }; 48 | return bowl; 49 | } 50 | 51 | function mix(bowl: any) { 52 | // Logic to mix the ingredients 53 | const batter = { 54 | /* mixed ingredients */ 55 | }; 56 | return batter; 57 | } 58 | 59 | function bake( 60 | cookieSheet: { batter: any; temp: number; minutes: number }, 61 | cb: (cookies: any) => void, 62 | ) { 63 | // Logic to bake the cookies and then we return them 64 | const cookies: any[] = [ 65 | /*Cook the cookies on the cookie sheet */ 66 | ]; 67 | cb(cookies); 68 | } 69 | 70 | function eat(cookies: any) { 71 | console.log('yummy!'); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /03-identify-async/begin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "end", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "keywords": [], 7 | "author": "", 8 | "license": "ISC", 9 | "scripts": { 10 | "dev": "concurrently \"npm run backend\" \"webpack-dev-server\"", 11 | "build": "webpack --mode production", 12 | "backend": "json-server --watch db.json --routes routes.json --port 8081 --middlewares ./node_modules/json-server-reset --delay 500" 13 | }, 14 | "dependencies": { 15 | "@fortawesome/fontawesome-svg-core": "^1.2.18", 16 | "@fortawesome/free-solid-svg-icons": "^5.8.2", 17 | "axios": "^0.19.0" 18 | }, 19 | "devDependencies": { 20 | "@typescript-eslint/eslint-plugin": "^2.3.2", 21 | "@typescript-eslint/parser": "^2.3.2", 22 | "bulma": "^0.9.3", 23 | "concurrently": "^4.1.2", 24 | "copy-webpack-plugin": "^6.4.1", 25 | "css-loader": "^3.2.0", 26 | "eslint": "^6.7.0", 27 | "eslint-config-prettier": "^6.7.0", 28 | "eslint-plugin-import": "^2.18.2", 29 | "eslint-plugin-prettier": "^3.1.1", 30 | "extract-text-webpack-plugin": "^4.0.0-beta.0", 31 | "json-server": "^0.15.1", 32 | "json-server-reset": "^1.2.0", 33 | "mini-css-extract-plugin": "^0.8.0", 34 | "sass": "^1.44.0", 35 | "prettier": "^1.19.1", 36 | "sass-loader": "^8.0.0", 37 | "style-loader": "^1.0.0", 38 | "ts-loader": "^6.2.0", 39 | "typescript": "^3.7.0-beta", 40 | "webpack": "^4.41.0", 41 | "webpack-cli": "^3.3.9", 42 | "webpack-dev-server": "^3.8.2" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /03-identify-async/begin/routes.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/*": "/$1", 3 | "/hero": "/heroes", 4 | "/heroes/:email": "/heroes?email=:email", 5 | "/order": "/orders", 6 | "/orders/:heroId": "/orders?heroId=:heroId", 7 | "/accountreps/:heroId": "/accountreps?heroId=:heroId", 8 | "/shippingstatuses/:orderNum": "/shippingstatuses?orderNum=:orderNum", 9 | "/reset": "/reset" 10 | } 11 | -------------------------------------------------------------------------------- /03-identify-async/begin/src/design/_bulma.scss: -------------------------------------------------------------------------------- 1 | @import 'bulma/bulma.sass'; 2 | 3 | // Using specific Bulma files reduces the 3 production css files from 195kb to 139kb for a 28% decrease 4 | // @import 'bulma/sass/utilities/_all.sass'; 5 | // @import 'bulma/sass/utilities/mixins.sass'; 6 | // // @import 'bulma/sass/utilities/controls.sass'; 7 | // // @import 'bulma/sass/base/generic.sass'; 8 | // // @import 'bulma/sass/base/helpers.sass'; // 277 lines 9 | // // @import 'bulma/sass/base/minireset.sass'; 10 | // // @import 'bulma/sass/layout/footer.sass'; 11 | // // @import 'bulma/sass/layout/section.sass'; // 12 lines 12 | // @import 'bulma/sass/components/card.sass'; 13 | // @import 'bulma/sass/components/list.sass'; // 39 lines 14 | // @import 'bulma/sass/components/menu.sass'; 15 | // // @import 'bulma/sass/components/modal.sass'; 16 | // @import 'bulma/sass/components/navbar.sass'; 17 | // // @import 'bulma/sass/elements/notification.sass'; 18 | // @import 'bulma/sass/elements/button.sass'; 19 | // @import 'bulma/sass/elements/container.sass'; // 25 lines 20 | // @import 'bulma/sass/elements/content.sass'; // 150 lines 21 | // @import 'bulma/sass/elements/title.sass'; // 64 lines 22 | // // // @import 'bulma/sass/elements/icon.sass'; 23 | // // @import 'bulma/sass/form/_all.sass'; 24 | // @import 'bulma/sass/grid/columns.sass'; 25 | -------------------------------------------------------------------------------- /03-identify-async/begin/src/design/_variables.scss: -------------------------------------------------------------------------------- 1 | $javascript: #f9e64f; 2 | $javascript-light: #fdf190; 3 | $typescript: #4386dd; //294e80 4 | $typescript-light: #4180d3; 5 | $primary: $typescript; 6 | $primary-light: $typescript-light; 7 | $link: $primary; 8 | $info: $primary; 9 | $shade-light: #fafafa; 10 | $accent-color: #00b3e6; 11 | $accent-dark-color: #294e80; 12 | $table-background-color: #b84949; //$shade-light; 13 | -------------------------------------------------------------------------------- /03-identify-async/begin/src/design/index.scss: -------------------------------------------------------------------------------- 1 | // @charset "utf-8"; 2 | // @import '~bulma/bulma'; 3 | @import 'variables'; 4 | @import 'bulma'; 5 | @import 'styles'; 6 | 7 | // Import a Google Font 8 | @import url('https://fonts.googleapis.com/css?family=Nunito:400,700'); 9 | @import url('https://use.fontawesome.com/releases/v5.4.1/css/all.css'); 10 | -------------------------------------------------------------------------------- /03-identify-async/begin/src/heroes.component.ts: -------------------------------------------------------------------------------- 1 | import { Hero, createDiv, cloneElementsFromTemplate, setText } from './lib'; 2 | 3 | export function replaceHeroListComponent(hero?: Hero) { 4 | const heroPlaceholder = document.querySelector('.hero-list'); 5 | const el = hero ? createList() : createNoneFound(); 6 | 7 | heroPlaceholder.replaceWith(el); 8 | 9 | function createList() { 10 | const ul = document.createElement('ul'); 11 | ul.classList.add('list', 'hero-list'); 12 | ul.appendChild(createHeroCardFromTemplate(hero)); 13 | return ul; 14 | } 15 | 16 | function createNoneFound() { 17 | const div = createDiv('hero-list'); 18 | div.innerText = 'No heroes found'; 19 | return div; 20 | } 21 | } 22 | 23 | /** 24 | * Code below here are private functions to this module 25 | * that support the replaceHeroListComponent function. 26 | **/ 27 | 28 | function createHeroCardFromTemplate(hero: Hero) { 29 | const heroClone = cloneElementsFromTemplate('hero-template'); 30 | setText(heroClone, '.description', hero.description); 31 | setText(heroClone, '.name', hero.name); 32 | setText(heroClone, '.email', hero.email); 33 | heroClone.querySelector('.card').classList.add(hero.name); 34 | return heroClone; 35 | } 36 | -------------------------------------------------------------------------------- /03-identify-async/begin/src/index.ts: -------------------------------------------------------------------------------- 1 | import './design/index.scss'; 2 | 3 | import { 4 | Hero, 5 | getHeroAsync, 6 | openModal, 7 | sayHelloTimer, 8 | showFetching, 9 | showMessage, 10 | } from './lib'; 11 | import { replaceHeroListComponent } from './heroes.component'; 12 | 13 | const searchEmailElement = document.getElementById( 14 | 'search-email', 15 | ) as HTMLInputElement; 16 | const button = document.querySelector('.search-button'); 17 | searchEmailElement.addEventListener('keydown', (e: KeyboardEvent) => { 18 | if (e.code === 'Enter') render(); 19 | }); 20 | button.addEventListener('click', render); 21 | 22 | document.querySelector('#open-modal').addEventListener('click', async () => { 23 | openModal().then((response: string) => { 24 | const msg = 25 | response === 'yes' 26 | ? `Yay! This is fun! 😄` 27 | : `Aw, that is sad. Let's try harder to have fun 😞`; 28 | 29 | showMessage(msg, 'Response from Modal'); 30 | }); 31 | }); 32 | 33 | document.querySelector('#run-timer').addEventListener('click', async () => { 34 | sayHelloTimer(1000); 35 | }); 36 | 37 | async function render() { 38 | showMessage(); 39 | showFetching('.hero-list'); 40 | let hero: Hero; 41 | try { 42 | hero = await getHeroAsync(searchEmailElement.value); 43 | } catch (error) { 44 | console.log(error); 45 | showMessage(error); 46 | } finally { 47 | replaceHeroListComponent(hero); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /03-identify-async/begin/src/lib/data.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | import { Hero } from './interfaces'; 4 | 5 | export const getHeroAsync = async function(email: string) { 6 | try { 7 | const response = await axios.get(`api/heroes?email=${email.toLowerCase()}`); 8 | const { data } = response; 9 | const hero: Hero = data[0]; 10 | return hero; 11 | } catch (error) { 12 | console.error(`Developer Error: Async Data Error: ${error.message}`); 13 | throw new Error(`Oh no! We're unable to fetch the Hero`); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /03-identify-async/begin/src/lib/dom.ts: -------------------------------------------------------------------------------- 1 | export function setText( 2 | el: DocumentFragment | HTMLElement, 3 | selector: string, 4 | text: string, 5 | ) { 6 | el.querySelector(selector).textContent = text.toString(); 7 | return el; 8 | } 9 | 10 | export function getText(el: DocumentFragment | HTMLElement, selector: string) { 11 | return el.querySelector(selector).textContent; 12 | } 13 | 14 | export const createDiv = (...classList: string[]) => { 15 | const el = document.createElement('div'); 16 | el.classList.add(...classList); 17 | return el; 18 | }; 19 | 20 | export function cloneElementsFromTemplate(templateName: string) { 21 | const template = document.getElementById(templateName) as HTMLTemplateElement; 22 | const clone = document.importNode(template.content, true); 23 | return clone; 24 | } 25 | 26 | export function showMessage(text = '', title = 'Info', append = false) { 27 | const el = document.getElementById('message-box'); 28 | el.style.visibility = !!text ? 'visible' : 'hidden'; 29 | 30 | let newText = text; 31 | if (append) { 32 | let oldText = getText(el, '.message-body'); 33 | newText = oldText ? `${oldText}\r\n${text}` : text; 34 | } 35 | 36 | setText(el, '.message-header', title); 37 | setText(el, '.message-body', newText); 38 | } 39 | 40 | export function showFetching(selector: string) { 41 | const progressClone = cloneElementsFromTemplate('progress-template'); 42 | const heroPlaceholder = document.querySelector(selector); 43 | heroPlaceholder.replaceWith(progressClone); 44 | } 45 | -------------------------------------------------------------------------------- /03-identify-async/begin/src/lib/index.ts: -------------------------------------------------------------------------------- 1 | export * from './data'; 2 | export * from './dom'; 3 | export * from './interfaces'; 4 | export * from './modal'; 5 | export * from './timer'; 6 | -------------------------------------------------------------------------------- /03-identify-async/begin/src/lib/interfaces.ts: -------------------------------------------------------------------------------- 1 | export interface Hero { 2 | id: number; 3 | name: string; 4 | description: string; 5 | email: string; 6 | orders?: Order[]; 7 | accountRep?: AccountRepresentative; 8 | } 9 | 10 | export interface Order { 11 | heroId: number; 12 | num: number; 13 | items: Item[]; 14 | shippingStatus: ShippingStatus; 15 | } 16 | 17 | export interface AccountRepresentative { 18 | repId: number; 19 | name: string; 20 | } 21 | 22 | export interface ShippingStatus { 23 | [index: number]: ShippingStatus; 24 | orderNum: number; 25 | status: string; 26 | } 27 | 28 | export interface Item { 29 | orderNum: number; 30 | name: string; 31 | qty: number; 32 | price: number; 33 | } 34 | 35 | export interface Callback { 36 | (data: T): void; 37 | } 38 | 39 | export interface CallbackError { 40 | (msg?: string): void; 41 | } 42 | -------------------------------------------------------------------------------- /03-identify-async/begin/src/lib/modal.ts: -------------------------------------------------------------------------------- 1 | export const openModal = async function() { 2 | const listenerYes = () => closeModal('yes'); 3 | const listenerNo = () => closeModal('no'); 4 | document.querySelector('.modal-yes').addEventListener('click', listenerYes); 5 | document.querySelector('.modal-no').addEventListener('click', listenerNo); 6 | 7 | const modalWindow = document.getElementById('modal'); 8 | 9 | if (modalWindow.classList) { 10 | modalWindow.classList.add('is-active'); 11 | } 12 | 13 | let resolve: (value?: string) => void; 14 | const responsePromise = new Promise((res, rej) => { 15 | resolve = res; 16 | }); 17 | /** 18 | * This next line returns the promise. 19 | * The caller then waits for the promise to resolve. 20 | */ 21 | return responsePromise; 22 | 23 | function closeModal(yesno: 'yes' | 'no') { 24 | if (modalWindow.classList) { 25 | modalWindow.classList.remove('is-active'); 26 | } 27 | 28 | modalWindow 29 | .querySelector('.modal-yes') 30 | .removeEventListener('click', listenerYes); 31 | modalWindow 32 | .querySelector('.modal-no') 33 | .removeEventListener('click', listenerNo); 34 | 35 | return resolve(yesno); 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /03-identify-async/begin/src/lib/timer.ts: -------------------------------------------------------------------------------- 1 | import { showMessage } from './dom'; 2 | 3 | export const sayHelloTimer = function(ms: number) { 4 | let counter = 0; 5 | showMessage(`Starting the timer`, 'Response from Timer'); 6 | 7 | const callback = (ms: number) => { 8 | counter++; 9 | showMessage( 10 | `Hello every ${ms} milliseconds. (${counter} iterations)`, 11 | 'Response from Timer', 12 | true, 13 | ); 14 | 15 | if (counter === 5) { 16 | showMessage( 17 | `Goodbye. We said hello every ${ms} milliseconds. (after ${counter} iterations)`, 18 | 'Response from Timer', 19 | true, 20 | ); 21 | clearInterval(intervalId); 22 | } 23 | }; 24 | 25 | const intervalId = setInterval(callback, ms, ms); 26 | }; 27 | -------------------------------------------------------------------------------- /03-identify-async/begin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist/", 4 | "sourceMap": true, 5 | "noImplicitAny": true, 6 | // "module": "es6", 7 | "module": "commonjs", 8 | "target": "es5", 9 | // "allowJs": true, 10 | "allowSyntheticDefaultImports": true, 11 | "lib": ["es2018", "dom"] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /03-identify-async/begin/webpack.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const path = require('path'); 3 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 4 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 5 | 6 | const index = 'index'; 7 | 8 | module.exports = { 9 | entry: `./src/${index}.ts`, 10 | devtool: 'inline-source-map', 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.tsx?$/, 15 | use: 'ts-loader', 16 | exclude: /node_modules/, 17 | }, 18 | { 19 | test: /\.s[ac]ss$/i, 20 | use: [ 21 | MiniCssExtractPlugin.loader, 22 | { 23 | loader: 'css-loader', 24 | }, 25 | { 26 | loader: 'sass-loader', 27 | options: { 28 | sourceMap: true, 29 | }, 30 | }, 31 | ], 32 | }, 33 | ], 34 | }, 35 | plugins: [ 36 | new MiniCssExtractPlugin({ 37 | filename: '[name].bundle.css', 38 | }), 39 | new CopyWebpackPlugin({ 40 | patterns: [ 41 | { 42 | from: `./${index}.html`, 43 | }, 44 | ], 45 | }), 46 | ], 47 | resolve: { 48 | extensions: ['.ts', '.js'], 49 | }, 50 | output: { 51 | filename: 'bundle.js', 52 | path: path.resolve(__dirname, 'dist'), 53 | }, 54 | performance: { 55 | maxEntrypointSize: 640000, 56 | maxAssetSize: 640000, 57 | }, 58 | devServer: { 59 | contentBase: 'dist', 60 | port: 8080, 61 | // Send API requests on localhost to API server get around CORS. 62 | proxy: { 63 | '/api': { 64 | target: { 65 | host: '0.0.0.0', 66 | protocol: 'http:', 67 | port: 8081, 68 | }, 69 | pathRewrite: { 70 | '^/api': '', 71 | }, 72 | }, 73 | }, 74 | }, 75 | }; 76 | -------------------------------------------------------------------------------- /03-identify-async/end/README.md: -------------------------------------------------------------------------------- 1 | Code is the same as BEGIN -------------------------------------------------------------------------------- /04-callback/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnpapa/typescript-async/bd7bfd359844248efa2b834b438f53dc36aec393/04-callback/README.md -------------------------------------------------------------------------------- /04-callback/begin/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: false, 5 | }, 6 | parser: '@typescript-eslint/parser', // Specifies the ESLint parser 7 | extends: [ 8 | // 'prettier', 9 | // Uses the recommended rules from the @typescript-eslint/eslint-plugin 10 | 'plugin:@typescript-eslint/recommended', 11 | // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier 12 | 'prettier/@typescript-eslint', 13 | // Enables eslint-plugin-prettier and displays prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array. 14 | 'plugin:prettier/recommended', 15 | ], 16 | plugins: ['@typescript-eslint', 'prettier'], 17 | // watch this for explaining why some of this is here 18 | // https://www.youtube.com/watch?time_continue=239&v=YIvjKId9m2c 19 | rules: { 20 | // 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 21 | 'no-console': 'off', 22 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', 23 | '@typescript-eslint/explicit-function-return-type': 'off', 24 | '@typescript-eslint/no-use-before-define': 'off', 25 | quotes: [2, 'single', { avoidEscape: true, allowTemplateLiterals: true }], 26 | 'prettier/prettier': [ 27 | 'error', 28 | { 29 | // trailingComma: 'all', 30 | // singleQuote: true, 31 | // printWidth: 80, 32 | }, 33 | ], 34 | }, 35 | }; 36 | -------------------------------------------------------------------------------- /04-callback/begin/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | /node_modules 10 | node_modules 11 | 12 | # IDEs and editors 13 | /.idea 14 | .project 15 | .classpath 16 | .c9/ 17 | *.launch 18 | .settings/ 19 | *.sublime-workspace 20 | 21 | # IDE - VSCode 22 | .vscode/* 23 | !.vscode/settings.json 24 | !.vscode/tasks.json 25 | !.vscode/launch.json 26 | !.vscode/extensions.json 27 | 28 | # misc 29 | /.sass-cache 30 | /connect.lock 31 | /coverage 32 | /libpeerconnection.log 33 | npm-debug.log 34 | yarn-error.log 35 | testem.log 36 | /typings 37 | 38 | # System Files 39 | .DS_Store 40 | Thumbs.db 41 | -------------------------------------------------------------------------------- /04-callback/begin/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 80, 3 | tabWidth: 2, 4 | bracketSpacing: true, 5 | singleQuote: true, 6 | trailingComma: 'all', 7 | }; 8 | -------------------------------------------------------------------------------- /04-callback/begin/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.enable": true, 3 | "workbench.colorCustomizations": { 4 | "activityBar.background": "#fbed80", 5 | "activityBar.activeBorder": "#06b9a5", 6 | "activityBar.foreground": "#15202b", 7 | "activityBar.inactiveForeground": "#15202b99", 8 | "activityBarBadge.background": "#06b9a5", 9 | "activityBarBadge.foreground": "#15202b", 10 | "titleBar.activeBackground": "#f9e64f", 11 | "titleBar.inactiveBackground": "#f9e64f99", 12 | "titleBar.activeForeground": "#15202b", 13 | "titleBar.inactiveForeground": "#15202b99", 14 | "statusBar.background": "#f9e64f", 15 | "statusBarItem.hoverBackground": "#f7df1e", 16 | "statusBar.foreground": "#15202b", 17 | "panel.border": "#fbed80", 18 | "sideBar.border": "#fbed80", 19 | "editorGroup.border": "#fbed80", 20 | "tab.activeBorder": "#fbed80" 21 | }, 22 | "peacock.color": "#f9e64f", 23 | "typescript.tsdk": "node_modules/typescript/lib" 24 | } -------------------------------------------------------------------------------- /04-callback/begin/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnpapa/typescript-async/bd7bfd359844248efa2b834b438f53dc36aec393/04-callback/begin/README.md -------------------------------------------------------------------------------- /04-callback/begin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "end", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "keywords": [], 7 | "author": "", 8 | "license": "ISC", 9 | "scripts": { 10 | "dev": "concurrently \"npm run backend\" \"webpack-dev-server\"", 11 | "build": "webpack --mode production", 12 | "backend": "json-server --watch db.json --routes routes.json --port 8081 --middlewares ./node_modules/json-server-reset --delay 500" 13 | }, 14 | "dependencies": { 15 | "@fortawesome/fontawesome-svg-core": "^1.2.18", 16 | "@fortawesome/free-solid-svg-icons": "^5.8.2", 17 | "axios": "^0.19.0" 18 | }, 19 | "devDependencies": { 20 | "@typescript-eslint/eslint-plugin": "^2.3.2", 21 | "@typescript-eslint/parser": "^2.3.2", 22 | "bulma": "^0.9.3", 23 | "concurrently": "^4.1.2", 24 | "copy-webpack-plugin": "^6.4.1", 25 | "css-loader": "^3.2.0", 26 | "eslint": "^6.7.0", 27 | "eslint-config-prettier": "^6.7.0", 28 | "eslint-plugin-import": "^2.18.2", 29 | "eslint-plugin-prettier": "^3.1.1", 30 | "extract-text-webpack-plugin": "^4.0.0-beta.0", 31 | "json-server": "^0.15.1", 32 | "json-server-reset": "^1.2.0", 33 | "mini-css-extract-plugin": "^0.8.0", 34 | "sass": "^1.44.0", 35 | "prettier": "^1.19.1", 36 | "sass-loader": "^8.0.0", 37 | "style-loader": "^1.0.0", 38 | "ts-loader": "^6.2.0", 39 | "typescript": "^3.7.0-beta", 40 | "webpack": "^4.41.0", 41 | "webpack-cli": "^3.3.9", 42 | "webpack-dev-server": "^3.8.2" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /04-callback/begin/routes.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/*": "/$1", 3 | "/hero": "/heroes", 4 | "/heroes/:email": "/heroes?email=:email", 5 | "/order": "/orders", 6 | "/orders/:heroId": "/orders?heroId=:heroId", 7 | "/accountreps/:heroId": "/accountreps?heroId=:heroId", 8 | "/shippingstatuses/:orderNum": "/shippingstatuses?orderNum=:orderNum", 9 | "/reset": "/reset" 10 | } 11 | -------------------------------------------------------------------------------- /04-callback/begin/src/design/_bulma.scss: -------------------------------------------------------------------------------- 1 | @import 'bulma/bulma.sass'; 2 | 3 | // Using specific Bulma files reduces the 3 production css files from 195kb to 139kb for a 28% decrease 4 | // @import 'bulma/sass/utilities/_all.sass'; 5 | // @import 'bulma/sass/utilities/mixins.sass'; 6 | // // @import 'bulma/sass/utilities/controls.sass'; 7 | // // @import 'bulma/sass/base/generic.sass'; 8 | // // @import 'bulma/sass/base/helpers.sass'; // 277 lines 9 | // // @import 'bulma/sass/base/minireset.sass'; 10 | // // @import 'bulma/sass/layout/footer.sass'; 11 | // // @import 'bulma/sass/layout/section.sass'; // 12 lines 12 | // @import 'bulma/sass/components/card.sass'; 13 | // @import 'bulma/sass/components/list.sass'; // 39 lines 14 | // @import 'bulma/sass/components/menu.sass'; 15 | // // @import 'bulma/sass/components/modal.sass'; 16 | // @import 'bulma/sass/components/navbar.sass'; 17 | // // @import 'bulma/sass/elements/notification.sass'; 18 | // @import 'bulma/sass/elements/button.sass'; 19 | // @import 'bulma/sass/elements/container.sass'; // 25 lines 20 | // @import 'bulma/sass/elements/content.sass'; // 150 lines 21 | // @import 'bulma/sass/elements/title.sass'; // 64 lines 22 | // // // @import 'bulma/sass/elements/icon.sass'; 23 | // // @import 'bulma/sass/form/_all.sass'; 24 | // @import 'bulma/sass/grid/columns.sass'; 25 | -------------------------------------------------------------------------------- /04-callback/begin/src/design/_variables.scss: -------------------------------------------------------------------------------- 1 | $javascript: #f9e64f; 2 | $javascript-light: #fdf190; 3 | $typescript: #4386dd; //294e80 4 | $typescript-light: #4180d3; 5 | $primary: $typescript; 6 | $primary-light: $typescript-light; 7 | $link: $primary; 8 | $info: $primary; 9 | $shade-light: #fafafa; 10 | $accent-color: #00b3e6; 11 | $accent-dark-color: #294e80; 12 | $table-background-color: #b84949; //$shade-light; 13 | -------------------------------------------------------------------------------- /04-callback/begin/src/design/index.scss: -------------------------------------------------------------------------------- 1 | // @charset "utf-8"; 2 | // @import '~bulma/bulma'; 3 | @import 'variables'; 4 | @import 'bulma'; 5 | @import 'styles'; 6 | 7 | // Import a Google Font 8 | @import url('https://fonts.googleapis.com/css?family=Nunito:400,700'); 9 | @import url('https://use.fontawesome.com/releases/v5.4.1/css/all.css'); 10 | -------------------------------------------------------------------------------- /04-callback/begin/src/examples/baking.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | /* eslint-disable @typescript-eslint/no-explicit-any */ 3 | /** 4 | * Baking Cookies 5 | * 6 | * This code does not run. 7 | * It is here as an example 8 | * of a mixed sync and async flow. 9 | */ 10 | 11 | import { ingredients } from './ingredients'; 12 | 13 | export function bakeCookies() { 14 | const bowl = combine(ingredients); 15 | 16 | const batter = mix(bowl); 17 | 18 | const cookieSheet = { batter, temp: 375, minutes: 10 }; 19 | 20 | bake(cookieSheet, cookies => { 21 | /** 22 | * we must wait 10 minutes for the oven 23 | * to bake the cookies 24 | * before we eat 25 | **/ 26 | eat(cookies); 27 | }); 28 | 29 | /** 30 | * private functions are below 31 | * 32 | **/ 33 | 34 | function combine(i: any) { 35 | // Logic to combine the ingredients 36 | const bowl = { 37 | /* combined indgredients */ 38 | }; 39 | return bowl; 40 | } 41 | 42 | function mix(bowl: any) { 43 | // Logic to mix the ingredients 44 | const batter = { 45 | /* mixed ingredients */ 46 | }; 47 | return batter; 48 | } 49 | 50 | function bake( 51 | cookieSheet: { batter: any; temp: number; minutes: number }, 52 | cb: (cookies: any) => void, 53 | ) { 54 | // Logic to bake the cookies and then we return them 55 | const cookies: any[] = [ 56 | /*Cook the cookies on the cookie sheet */ 57 | ]; 58 | cb(cookies); 59 | } 60 | 61 | function eat(cookies: any) { 62 | console.log('yummy!'); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /04-callback/begin/src/examples/get-ingredients.ts: -------------------------------------------------------------------------------- 1 | import { ingredients } from './ingredients'; 2 | 3 | /** 4 | * TODO 5 | * Wait for a delay, get the ingredients, 6 | * then pass them in a callback function 7 | */ 8 | -------------------------------------------------------------------------------- /04-callback/begin/src/examples/ingredients.ts: -------------------------------------------------------------------------------- 1 | export const ingredients = [ 2 | '2 1/4 cups all-purpose flour', 3 | '1 teaspoon baking soda', 4 | '1 teaspoon salt', 5 | '1 cup (2 sticks) butter', 6 | '3/4 cup granulated sugar', 7 | '3/4 cup packed brown sugar', 8 | '1 teaspoon vanilla extract', 9 | '2 large eggs', 10 | '2 cups (12-oz. pkg.) chocolate chips', 11 | ]; 12 | -------------------------------------------------------------------------------- /04-callback/begin/src/examples/sync-and-async.ts: -------------------------------------------------------------------------------- 1 | import { showMessage } from '../lib'; 2 | import { ingredients } from './ingredients'; 3 | 4 | export function forEachExample() { 5 | let index = 0; 6 | ingredients.forEach(name => { 7 | index++; 8 | showMessage(`${index} - ${name}`); 9 | console.log(`${index} - ${name}`); 10 | }); 11 | } 12 | 13 | export function setTimeoutExample() { 14 | console.clear(); 15 | showMessage('Me: How are you?'); 16 | console.log('Me: How are you?'); 17 | setTimeout(() => { 18 | showMessage('You: I am fine, thank you! How are you?'); 19 | console.log('You: I am fine, thank you! How are you?'); 20 | setTimeout(() => { 21 | showMessage('Me: Well, thanks!'); 22 | console.log('Me: Well, thanks!'); 23 | setTimeout(() => { 24 | showMessage(`You: That's good to hear!`); 25 | console.log(`You: That's good to hear!`); 26 | }, 1500); 27 | }, 1500); 28 | }, 1500); 29 | } 30 | -------------------------------------------------------------------------------- /04-callback/begin/src/heroes.component.ts: -------------------------------------------------------------------------------- 1 | import { Hero, createDiv, cloneElementsFromTemplate, setText } from './lib'; 2 | 3 | export function replaceHeroListComponent(hero?: Hero) { 4 | const heroPlaceholder = document.querySelector('.hero-list'); 5 | const el = hero ? createList() : createNoneFound(); 6 | 7 | heroPlaceholder.replaceWith(el); 8 | 9 | function createList() { 10 | const ul = document.createElement('ul'); 11 | ul.classList.add('list', 'hero-list'); 12 | ul.appendChild(createHeroCardFromTemplate(hero)); 13 | return ul; 14 | } 15 | 16 | function createNoneFound() { 17 | const div = createDiv('hero-list'); 18 | div.innerText = 'No heroes found'; 19 | return div; 20 | } 21 | } 22 | 23 | /** 24 | * Code below here are private functions to this module 25 | * that support the replaceHeroListComponent function. 26 | **/ 27 | 28 | function createHeroCardFromTemplate(hero: Hero) { 29 | const heroClone = cloneElementsFromTemplate('hero-template'); 30 | setText(heroClone, '.description', hero.description); 31 | setText(heroClone, '.name', hero.name); 32 | setText(heroClone, '.email', hero.email); 33 | heroClone.querySelector('.card').classList.add(hero.name); 34 | return heroClone; 35 | } 36 | -------------------------------------------------------------------------------- /04-callback/begin/src/index.ts: -------------------------------------------------------------------------------- 1 | import './design/index.scss'; 2 | 3 | import { showFetching, showMessage } from './lib'; 4 | import { replaceHeroListComponent } from './heroes.component'; 5 | 6 | const searchEmailElement = document.getElementById( 7 | 'search-email', 8 | ) as HTMLInputElement; 9 | const button = document.querySelector('.search-button'); 10 | searchEmailElement.addEventListener('keydown', (e: KeyboardEvent) => { 11 | if (e.code === 'Enter') render(); 12 | }); 13 | button.addEventListener('click', render); 14 | 15 | /** 16 | * Show Ingredients 17 | * 18 | * Concepts: 19 | * Call a callback function. 20 | * Create a callback function. 21 | */ 22 | document 23 | .querySelector('#show-ingredients') 24 | .addEventListener('click', getIngredients); 25 | 26 | function getIngredients() { 27 | showMessage('Ingredients for baking amazing cookies:', 'Ingredients'); 28 | 29 | // TODO: Get the ingredients and display them 30 | } 31 | 32 | /** 33 | * Render the heroes list. 34 | */ 35 | async function render() { 36 | showMessage(); 37 | showFetching('.hero-list'); 38 | 39 | /** 40 | * TODO: 41 | * Get the heroes. 42 | * If it works, display them. 43 | * If it fails, display an error. 44 | */ 45 | } 46 | -------------------------------------------------------------------------------- /04-callback/begin/src/lib/data/callback.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosResponse, AxiosError } from 'axios'; 2 | 3 | import { 4 | Order, 5 | Callback, 6 | CallbackError, 7 | Hero, 8 | AccountRepresentative, 9 | } from '../interfaces'; 10 | import { apiUrl, parseList } from './config'; 11 | 12 | /** 13 | * TODO: Create a function that gets the hero 14 | * and all of his/her related data. 15 | * Use callbacks for both success and error conditions. 16 | */ 17 | 18 | const getHeroCallback = function( 19 | email: string, 20 | callback: Callback, 21 | callbackError?: CallbackError, 22 | ) { 23 | axios 24 | .get(`${apiUrl}/heroes?email=${email}`) 25 | .then((response: AxiosResponse) => { 26 | const data = parseList(response); 27 | const hero = data[0]; 28 | callback(hero); 29 | }) 30 | .catch((error: AxiosError) => { 31 | console.error(`Developer Error: Async Data Error: ${error.message}`); 32 | callbackError(`Oh no! We're unable to fetch the Hero`); 33 | }); 34 | }; 35 | 36 | const getOrdersCallback = function( 37 | heroId: number, 38 | callback: Callback, 39 | callbackError?: CallbackError, 40 | ) { 41 | const url = heroId ? `${apiUrl}/orders/${heroId}` : `${apiUrl}/orders`; 42 | axios 43 | .get(url) 44 | .then((response: AxiosResponse) => { 45 | const orders = parseList(response); 46 | callback(orders); 47 | }) 48 | .catch((error: AxiosError) => { 49 | console.error(`Developer Error: Async Data Error: ${error.message}`); 50 | callbackError(`Oh no! We're unable to fetch the Orders`); 51 | }); 52 | }; 53 | 54 | const getAccountRepCallback = function( 55 | heroId: number, 56 | callback: Callback, 57 | callbackError?: CallbackError, 58 | ) { 59 | const url = `${apiUrl}/accountreps/${heroId}`; 60 | axios 61 | .get(url) 62 | .then((response: AxiosResponse) => { 63 | const list = parseList(response); 64 | const accountRep = list[0]; 65 | callback(accountRep); 66 | }) 67 | .catch((error: AxiosError) => { 68 | console.error(`Developer Error: Async Data Error: ${error.message}`); 69 | callbackError(`Oh no! We're unable to fetch the Account Rep`); 70 | }); 71 | }; 72 | -------------------------------------------------------------------------------- /04-callback/begin/src/lib/data/config.ts: -------------------------------------------------------------------------------- 1 | import { AxiosResponse } from 'axios'; 2 | 3 | export const API = '/api'; 4 | 5 | let apiUrl = API; 6 | 7 | const parseList = (response: AxiosResponse) => { 8 | if (response.status !== 200) throw Error(response.statusText); 9 | if (!response.data) return []; 10 | let list: T[] = response.data; 11 | if (typeof list !== 'object') { 12 | list = []; 13 | } 14 | return list; 15 | }; 16 | 17 | export { apiUrl, parseList }; 18 | -------------------------------------------------------------------------------- /04-callback/begin/src/lib/data/index.ts: -------------------------------------------------------------------------------- 1 | export * from './config'; 2 | export * from './callback'; 3 | -------------------------------------------------------------------------------- /04-callback/begin/src/lib/dom.ts: -------------------------------------------------------------------------------- 1 | export function setText( 2 | el: DocumentFragment | HTMLElement, 3 | selector: string, 4 | text: string, 5 | ) { 6 | el.querySelector(selector).textContent = text.toString(); 7 | return el; 8 | } 9 | 10 | export function getText(el: DocumentFragment | HTMLElement, selector: string) { 11 | return el.querySelector(selector).textContent; 12 | } 13 | 14 | export const createDiv = (...classList: string[]) => { 15 | const el = document.createElement('div'); 16 | el.classList.add(...classList); 17 | return el; 18 | }; 19 | 20 | export function cloneElementsFromTemplate(templateName: string) { 21 | const template = document.getElementById(templateName) as HTMLTemplateElement; 22 | const clone = document.importNode(template.content, true); 23 | return clone; 24 | } 25 | 26 | export function showMessage(text = '', title = 'Info', append = false) { 27 | const el = document.getElementById('message-box'); 28 | el.style.visibility = !!text ? 'visible' : 'hidden'; 29 | 30 | let newText = text; 31 | if (append) { 32 | let oldText = getText(el, '.message-body'); 33 | newText = `${oldText}\r\n${text}`; 34 | } 35 | 36 | setText(el, '.message-header', title); 37 | setText(el, '.message-body', newText); 38 | } 39 | 40 | export function showFetching(selector: string) { 41 | const progressClone = cloneElementsFromTemplate('progress-template'); 42 | const heroPlaceholder = document.querySelector(selector); 43 | heroPlaceholder.replaceWith(progressClone); 44 | } 45 | -------------------------------------------------------------------------------- /04-callback/begin/src/lib/index.ts: -------------------------------------------------------------------------------- 1 | export * from './data'; 2 | export * from './dom'; 3 | export * from './interfaces'; 4 | export * from './modal'; 5 | export * from './timer'; 6 | -------------------------------------------------------------------------------- /04-callback/begin/src/lib/interfaces.ts: -------------------------------------------------------------------------------- 1 | export interface Hero { 2 | id: number; 3 | name: string; 4 | description: string; 5 | email: string; 6 | orders?: Order[]; 7 | accountRep?: AccountRepresentative; 8 | } 9 | 10 | export interface Order { 11 | heroId: number; 12 | num: number; 13 | items: Item[]; 14 | shippingStatus: ShippingStatus; 15 | } 16 | 17 | export interface AccountRepresentative { 18 | repId: number; 19 | name: string; 20 | } 21 | 22 | export interface ShippingStatus { 23 | [index: number]: ShippingStatus; 24 | orderNum: number; 25 | status: string; 26 | } 27 | 28 | export interface Item { 29 | orderNum: number; 30 | name: string; 31 | qty: number; 32 | price: number; 33 | } 34 | 35 | export interface Callback { 36 | (data: T): void; 37 | } 38 | 39 | export interface CallbackError { 40 | (msg?: string): void; 41 | } 42 | -------------------------------------------------------------------------------- /04-callback/begin/src/lib/modal.ts: -------------------------------------------------------------------------------- 1 | export const openModal = async function () { 2 | const listenerYes = () => closeModal('yes'); 3 | const listenerNo = () => closeModal('no'); 4 | document.querySelector('.modal-yes').addEventListener('click', listenerYes); 5 | document.querySelector('.modal-no').addEventListener('click', listenerNo); 6 | 7 | const modalWindow = document.getElementById('modal'); 8 | 9 | if (modalWindow.classList) { 10 | modalWindow.classList.add('is-active'); 11 | } 12 | 13 | let resolve: (value?: string) => void; 14 | const responsePromise = new Promise((res, rej) => { 15 | resolve = res; 16 | }); 17 | /** 18 | * This next line returns the promise. 19 | * The caller then waits for the promise to resolve. 20 | */ 21 | return responsePromise; 22 | 23 | function closeModal(yesno: 'yes' | 'no') { 24 | if (modalWindow.classList) { 25 | modalWindow.classList.remove('is-active'); 26 | } 27 | 28 | modalWindow 29 | .querySelector('.modal-yes') 30 | .removeEventListener('click', listenerYes); 31 | modalWindow 32 | .querySelector('.modal-no') 33 | .removeEventListener('click', listenerNo); 34 | 35 | return resolve(yesno); 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /04-callback/begin/src/lib/timer.ts: -------------------------------------------------------------------------------- 1 | import { showMessage } from './dom'; 2 | 3 | export const sayHelloTimer = function(ms: number) { 4 | let counter = 0; 5 | showMessage(`Starting the timer`, 'Response from Timer'); 6 | 7 | const callback = (ms: number) => { 8 | counter++; 9 | showMessage( 10 | `Hello every ${ms} milliseconds. (${counter} iterations)`, 11 | 'Response from Timer', 12 | true, 13 | ); 14 | 15 | if (counter === 5) { 16 | showMessage( 17 | `Goodbye. We said hello every ${ms} milliseconds. (after ${counter} iterations)`, 18 | 'Response from Timer', 19 | true, 20 | ); 21 | clearInterval(intervalId); 22 | } 23 | }; 24 | 25 | const intervalId = setInterval(callback, ms, ms); 26 | }; 27 | -------------------------------------------------------------------------------- /04-callback/begin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist/", 4 | "sourceMap": true, 5 | "noImplicitAny": true, 6 | // "module": "es6", 7 | "module": "commonjs", 8 | "target": "es5", 9 | // "allowJs": true, 10 | "allowSyntheticDefaultImports": true, 11 | "lib": ["es2018", "dom"] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /04-callback/begin/webpack.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const path = require('path'); 3 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 4 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 5 | 6 | const index = 'index'; 7 | 8 | module.exports = { 9 | entry: `./src/${index}.ts`, 10 | devtool: 'inline-source-map', 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.tsx?$/, 15 | use: 'ts-loader', 16 | exclude: /node_modules/, 17 | }, 18 | { 19 | test: /\.s[ac]ss$/i, 20 | use: [ 21 | MiniCssExtractPlugin.loader, 22 | { 23 | loader: 'css-loader', 24 | }, 25 | { 26 | loader: 'sass-loader', 27 | options: { 28 | sourceMap: true, 29 | }, 30 | }, 31 | ], 32 | }, 33 | ], 34 | }, 35 | plugins: [ 36 | new MiniCssExtractPlugin({ 37 | filename: '[name].bundle.css', 38 | }), 39 | new CopyWebpackPlugin({ 40 | patterns: [ 41 | { 42 | from: `./${index}.html`, 43 | }, 44 | ], 45 | }), 46 | ], 47 | resolve: { 48 | extensions: ['.ts', '.js'], 49 | }, 50 | output: { 51 | filename: 'bundle.js', 52 | path: path.resolve(__dirname, 'dist'), 53 | }, 54 | performance: { 55 | maxEntrypointSize: 640000, 56 | maxAssetSize: 640000, 57 | }, 58 | devServer: { 59 | contentBase: 'dist', 60 | port: 8080, 61 | // Send API requests on localhost to API server get around CORS. 62 | proxy: { 63 | '/api': { 64 | target: { 65 | host: '0.0.0.0', 66 | protocol: 'http:', 67 | port: 8081, 68 | }, 69 | pathRewrite: { 70 | '^/api': '', 71 | }, 72 | }, 73 | }, 74 | }, 75 | }; 76 | -------------------------------------------------------------------------------- /04-callback/end/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: false, 5 | }, 6 | parser: '@typescript-eslint/parser', // Specifies the ESLint parser 7 | extends: [ 8 | // 'prettier', 9 | // Uses the recommended rules from the @typescript-eslint/eslint-plugin 10 | 'plugin:@typescript-eslint/recommended', 11 | // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier 12 | 'prettier/@typescript-eslint', 13 | // Enables eslint-plugin-prettier and displays prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array. 14 | 'plugin:prettier/recommended', 15 | ], 16 | plugins: ['@typescript-eslint', 'prettier'], 17 | // watch this for explaining why some of this is here 18 | // https://www.youtube.com/watch?time_continue=239&v=YIvjKId9m2c 19 | rules: { 20 | // 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 21 | 'no-console': 'off', 22 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', 23 | '@typescript-eslint/explicit-function-return-type': 'off', 24 | '@typescript-eslint/no-use-before-define': 'off', 25 | quotes: [2, 'single', { avoidEscape: true, allowTemplateLiterals: true }], 26 | 'prettier/prettier': [ 27 | 'error', 28 | { 29 | // trailingComma: 'all', 30 | // singleQuote: true, 31 | // printWidth: 80, 32 | }, 33 | ], 34 | }, 35 | }; 36 | -------------------------------------------------------------------------------- /04-callback/end/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | /node_modules 10 | node_modules 11 | 12 | # IDEs and editors 13 | /.idea 14 | .project 15 | .classpath 16 | .c9/ 17 | *.launch 18 | .settings/ 19 | *.sublime-workspace 20 | 21 | # IDE - VSCode 22 | .vscode/* 23 | !.vscode/settings.json 24 | !.vscode/tasks.json 25 | !.vscode/launch.json 26 | !.vscode/extensions.json 27 | 28 | # misc 29 | /.sass-cache 30 | /connect.lock 31 | /coverage 32 | /libpeerconnection.log 33 | npm-debug.log 34 | yarn-error.log 35 | testem.log 36 | /typings 37 | 38 | # System Files 39 | .DS_Store 40 | Thumbs.db 41 | -------------------------------------------------------------------------------- /04-callback/end/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 80, 3 | tabWidth: 2, 4 | bracketSpacing: true, 5 | singleQuote: true, 6 | trailingComma: 'all', 7 | }; 8 | -------------------------------------------------------------------------------- /04-callback/end/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.enable": true, 3 | "workbench.colorCustomizations": { 4 | "activityBar.background": "#fbed80", 5 | "activityBar.activeBorder": "#06b9a5", 6 | "activityBar.foreground": "#15202b", 7 | "activityBar.inactiveForeground": "#15202b99", 8 | "activityBarBadge.background": "#06b9a5", 9 | "activityBarBadge.foreground": "#15202b", 10 | "titleBar.activeBackground": "#f9e64f", 11 | "titleBar.inactiveBackground": "#f9e64f99", 12 | "titleBar.activeForeground": "#15202b", 13 | "titleBar.inactiveForeground": "#15202b99", 14 | "statusBar.background": "#f9e64f", 15 | "statusBarItem.hoverBackground": "#f7df1e", 16 | "statusBar.foreground": "#15202b", 17 | "panel.border": "#fbed80", 18 | "sideBar.border": "#fbed80", 19 | "editorGroup.border": "#fbed80", 20 | "tab.activeBorder": "#fbed80" 21 | }, 22 | "peacock.color": "#f9e64f", 23 | "typescript.tsdk": "node_modules/typescript/lib" 24 | } -------------------------------------------------------------------------------- /04-callback/end/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnpapa/typescript-async/bd7bfd359844248efa2b834b438f53dc36aec393/04-callback/end/README.md -------------------------------------------------------------------------------- /04-callback/end/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "end", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "keywords": [], 7 | "author": "", 8 | "license": "ISC", 9 | "scripts": { 10 | "dev": "concurrently \"npm run backend\" \"webpack-dev-server\"", 11 | "build": "webpack --mode production", 12 | "backend": "json-server --watch db.json --routes routes.json --port 8081 --middlewares ./node_modules/json-server-reset --delay 500" 13 | }, 14 | "dependencies": { 15 | "@fortawesome/fontawesome-svg-core": "^1.2.18", 16 | "@fortawesome/free-solid-svg-icons": "^5.8.2", 17 | "axios": "^0.19.0" 18 | }, 19 | "devDependencies": { 20 | "@typescript-eslint/eslint-plugin": "^2.3.2", 21 | "@typescript-eslint/parser": "^2.3.2", 22 | "bulma": "^0.9.3", 23 | "concurrently": "^4.1.2", 24 | "copy-webpack-plugin": "^6.4.1", 25 | "css-loader": "^3.2.0", 26 | "eslint": "^6.7.0", 27 | "eslint-config-prettier": "^6.7.0", 28 | "eslint-plugin-import": "^2.18.2", 29 | "eslint-plugin-prettier": "^3.1.1", 30 | "extract-text-webpack-plugin": "^4.0.0-beta.0", 31 | "json-server": "^0.15.1", 32 | "json-server-reset": "^1.2.0", 33 | "mini-css-extract-plugin": "^0.8.0", 34 | "sass": "^1.44.0", 35 | "prettier": "^1.19.1", 36 | "sass-loader": "^8.0.0", 37 | "style-loader": "^1.0.0", 38 | "ts-loader": "^6.2.0", 39 | "typescript": "^3.7.0-beta", 40 | "webpack": "^4.41.0", 41 | "webpack-cli": "^3.3.9", 42 | "webpack-dev-server": "^3.8.2" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /04-callback/end/routes.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/*": "/$1", 3 | "/hero": "/heroes", 4 | "/heroes/:email": "/heroes?email=:email", 5 | "/order": "/orders", 6 | "/orders/:heroId": "/orders?heroId=:heroId", 7 | "/accountreps/:heroId": "/accountreps?heroId=:heroId", 8 | "/shippingstatuses/:orderNum": "/shippingstatuses?orderNum=:orderNum", 9 | "/reset": "/reset" 10 | } 11 | -------------------------------------------------------------------------------- /04-callback/end/src/design/_bulma.scss: -------------------------------------------------------------------------------- 1 | @import 'bulma/bulma.sass'; 2 | 3 | // Using specific Bulma files reduces the 3 production css files from 195kb to 139kb for a 28% decrease 4 | // @import 'bulma/sass/utilities/_all.sass'; 5 | // @import 'bulma/sass/utilities/mixins.sass'; 6 | // // @import 'bulma/sass/utilities/controls.sass'; 7 | // // @import 'bulma/sass/base/generic.sass'; 8 | // // @import 'bulma/sass/base/helpers.sass'; // 277 lines 9 | // // @import 'bulma/sass/base/minireset.sass'; 10 | // // @import 'bulma/sass/layout/footer.sass'; 11 | // // @import 'bulma/sass/layout/section.sass'; // 12 lines 12 | // @import 'bulma/sass/components/card.sass'; 13 | // @import 'bulma/sass/components/list.sass'; // 39 lines 14 | // @import 'bulma/sass/components/menu.sass'; 15 | // // @import 'bulma/sass/components/modal.sass'; 16 | // @import 'bulma/sass/components/navbar.sass'; 17 | // // @import 'bulma/sass/elements/notification.sass'; 18 | // @import 'bulma/sass/elements/button.sass'; 19 | // @import 'bulma/sass/elements/container.sass'; // 25 lines 20 | // @import 'bulma/sass/elements/content.sass'; // 150 lines 21 | // @import 'bulma/sass/elements/title.sass'; // 64 lines 22 | // // // @import 'bulma/sass/elements/icon.sass'; 23 | // // @import 'bulma/sass/form/_all.sass'; 24 | // @import 'bulma/sass/grid/columns.sass'; 25 | -------------------------------------------------------------------------------- /04-callback/end/src/design/_variables.scss: -------------------------------------------------------------------------------- 1 | $javascript: #f9e64f; 2 | $javascript-light: #fdf190; 3 | $typescript: #4386dd; //294e80 4 | $typescript-light: #4180d3; 5 | $primary: $typescript; 6 | $primary-light: $typescript-light; 7 | $link: $primary; 8 | $info: $primary; 9 | $shade-light: #fafafa; 10 | $accent-color: #00b3e6; 11 | $accent-dark-color: #294e80; 12 | $table-background-color: #b84949; //$shade-light; 13 | -------------------------------------------------------------------------------- /04-callback/end/src/design/index.scss: -------------------------------------------------------------------------------- 1 | // @charset "utf-8"; 2 | // @import '~bulma/bulma'; 3 | @import 'variables'; 4 | @import 'bulma'; 5 | @import 'styles'; 6 | 7 | // Import a Google Font 8 | @import url('https://fonts.googleapis.com/css?family=Nunito:400,700'); 9 | @import url('https://use.fontawesome.com/releases/v5.4.1/css/all.css'); 10 | -------------------------------------------------------------------------------- /04-callback/end/src/examples/baking.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | /* eslint-disable @typescript-eslint/no-explicit-any */ 3 | /** 4 | * Baking Cookies 5 | * 6 | * This code does not run. 7 | * It is here as an example 8 | * of a mixed sync and async flow. 9 | */ 10 | 11 | import { ingredients } from './ingredients'; 12 | 13 | export function bakeCookies() { 14 | const bowl = combine(ingredients); 15 | 16 | const batter = mix(bowl); 17 | 18 | const cookieSheet = { batter, temp: 375, minutes: 10 }; 19 | 20 | bake(cookieSheet, cookies => { 21 | /** 22 | * we must wait 10 minutes for the oven 23 | * to bake the cookies 24 | * before we eat 25 | **/ 26 | eat(cookies); 27 | }); 28 | 29 | /** 30 | * private functions are below 31 | * 32 | **/ 33 | 34 | function combine(i: any) { 35 | // Logic to combine the ingredients 36 | const bowl = { 37 | /* combined indgredients */ 38 | }; 39 | return bowl; 40 | } 41 | 42 | function mix(bowl: any) { 43 | // Logic to mix the ingredients 44 | const batter = { 45 | /* mixed ingredients */ 46 | }; 47 | return batter; 48 | } 49 | 50 | function bake( 51 | cookieSheet: { batter: any; temp: number; minutes: number }, 52 | cb: (cookies: any) => void, 53 | ) { 54 | // Logic to bake the cookies and then we return them 55 | const cookies: any[] = [ 56 | /*Cook the cookies on the cookie sheet */ 57 | ]; 58 | cb(cookies); 59 | } 60 | 61 | function eat(cookies: any) { 62 | console.log('yummy!'); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /04-callback/end/src/examples/get-ingredients.ts: -------------------------------------------------------------------------------- 1 | import { ingredients } from './ingredients'; 2 | 3 | export function getDataAfterDelay( 4 | delayMs: number, 5 | callback: (data: string[]) => void, 6 | ) { 7 | setTimeout(() => { 8 | const data = ingredients; 9 | callback(data); 10 | }, delayMs); 11 | } 12 | -------------------------------------------------------------------------------- /04-callback/end/src/examples/ingredients.ts: -------------------------------------------------------------------------------- 1 | export const ingredients = [ 2 | '2 1/4 cups all-purpose flour', 3 | '1 teaspoon baking soda', 4 | '1 teaspoon salt', 5 | '1 cup (2 sticks) butter', 6 | '3/4 cup granulated sugar', 7 | '3/4 cup packed brown sugar', 8 | '1 teaspoon vanilla extract', 9 | '2 large eggs', 10 | '2 cups (12-oz. pkg.) chocolate chips', 11 | ]; 12 | -------------------------------------------------------------------------------- /04-callback/end/src/examples/sync-and-async.ts: -------------------------------------------------------------------------------- 1 | import { showMessage } from '../lib'; 2 | import { ingredients } from './ingredients'; 3 | 4 | export function forEachExample() { 5 | let index = 0; 6 | ingredients.forEach(name => { 7 | index++; 8 | showMessage(`${index} - ${name}`); 9 | console.log(`${index} - ${name}`); 10 | }); 11 | } 12 | 13 | export function setTimeoutExample() { 14 | console.clear(); 15 | showMessage('Me: How are you?'); 16 | console.log('Me: How are you?'); 17 | setTimeout(() => { 18 | showMessage('You: I am fine, thank you! How are you?'); 19 | console.log('You: I am fine, thank you! How are you?'); 20 | setTimeout(() => { 21 | showMessage('Me: Well, thanks!'); 22 | console.log('Me: Well, thanks!'); 23 | setTimeout(() => { 24 | showMessage(`You: That's good to hear!`); 25 | console.log(`You: That's good to hear!`); 26 | }, 1500); 27 | }, 1500); 28 | }, 1500); 29 | } 30 | -------------------------------------------------------------------------------- /04-callback/end/src/heroes.component.ts: -------------------------------------------------------------------------------- 1 | import { Hero, createDiv, cloneElementsFromTemplate, setText } from './lib'; 2 | 3 | export function replaceHeroListComponent(hero?: Hero) { 4 | const heroPlaceholder = document.querySelector('.hero-list'); 5 | const el = hero ? createList() : createNoneFound(); 6 | 7 | heroPlaceholder.replaceWith(el); 8 | 9 | function createList() { 10 | const ul = document.createElement('ul'); 11 | ul.classList.add('list', 'hero-list'); 12 | ul.appendChild(createHeroCardFromTemplate(hero)); 13 | return ul; 14 | } 15 | 16 | function createNoneFound() { 17 | const div = createDiv('hero-list'); 18 | div.innerText = 'No heroes found'; 19 | return div; 20 | } 21 | } 22 | 23 | /** 24 | * Code below here are private functions to this module 25 | * that support the replaceHeroListComponent function. 26 | **/ 27 | 28 | function createHeroCardFromTemplate(hero: Hero) { 29 | const heroClone = cloneElementsFromTemplate('hero-template'); 30 | setText(heroClone, '.description', hero.description); 31 | setText(heroClone, '.name', hero.name); 32 | setText(heroClone, '.email', hero.email); 33 | heroClone.querySelector('.card').classList.add(hero.name); 34 | return heroClone; 35 | } 36 | -------------------------------------------------------------------------------- /04-callback/end/src/index.ts: -------------------------------------------------------------------------------- 1 | import './design/index.scss'; 2 | 3 | import { Hero, showFetching, showMessage, getHeroTreeCallback } from './lib'; 4 | import { getDataAfterDelay } from './examples/get-ingredients'; 5 | import { replaceHeroListComponent } from './heroes.component'; 6 | 7 | const searchEmailElement = document.getElementById( 8 | 'search-email', 9 | ) as HTMLInputElement; 10 | const button = document.querySelector('.search-button'); 11 | searchEmailElement.addEventListener('keydown', (e: KeyboardEvent) => { 12 | if (e.code === 'Enter') render(); 13 | }); 14 | button.addEventListener('click', render); 15 | 16 | /** 17 | * Show Ingredients 18 | * 19 | * Concepts: 20 | * Call a callback function. 21 | * Create a callback function. 22 | */ 23 | document 24 | .querySelector('#show-ingredients') 25 | .addEventListener('click', getIngredients); 26 | 27 | function getIngredients() { 28 | showMessage('Ingredients for baking amazing cookies:', 'Ingredients'); 29 | getDataAfterDelay(1500, showIngredients); 30 | 31 | function showIngredients(ingredients: string[]) { 32 | ingredients.forEach(i => showMessage(` ${i}`, 'Ingredients', true)); 33 | } 34 | } 35 | 36 | /** 37 | * Render the heroes list. 38 | */ 39 | async function render() { 40 | showMessage(); 41 | showFetching('.hero-list'); 42 | getHeroTreeCallback( 43 | searchEmailElement.value, 44 | 45 | (hero: Hero) => { 46 | replaceHeroListComponent(hero); 47 | }, 48 | 49 | (errorMsg: string) => { 50 | console.log(errorMsg); 51 | showMessage(errorMsg); 52 | replaceHeroListComponent(); 53 | }, 54 | ); 55 | } 56 | -------------------------------------------------------------------------------- /04-callback/end/src/lib/data/config.ts: -------------------------------------------------------------------------------- 1 | import { AxiosResponse } from 'axios'; 2 | 3 | export const API = '/api'; 4 | 5 | let apiUrl = API; 6 | 7 | const parseList = (response: AxiosResponse) => { 8 | if (response.status !== 200) throw Error(response.statusText); 9 | if (!response.data) return []; 10 | let list: T[] = response.data; 11 | if (typeof list !== 'object') { 12 | list = []; 13 | } 14 | return list; 15 | }; 16 | 17 | export { apiUrl, parseList }; 18 | -------------------------------------------------------------------------------- /04-callback/end/src/lib/data/index.ts: -------------------------------------------------------------------------------- 1 | export * from './config'; 2 | export * from './callback'; 3 | -------------------------------------------------------------------------------- /04-callback/end/src/lib/dom.ts: -------------------------------------------------------------------------------- 1 | export function setText( 2 | el: DocumentFragment | HTMLElement, 3 | selector: string, 4 | text: string, 5 | ) { 6 | el.querySelector(selector).textContent = text.toString(); 7 | return el; 8 | } 9 | 10 | export function getText(el: DocumentFragment | HTMLElement, selector: string) { 11 | return el.querySelector(selector).textContent; 12 | } 13 | 14 | export const createDiv = (...classList: string[]) => { 15 | const el = document.createElement('div'); 16 | el.classList.add(...classList); 17 | return el; 18 | }; 19 | 20 | export function cloneElementsFromTemplate(templateName: string) { 21 | const template = document.getElementById(templateName) as HTMLTemplateElement; 22 | const clone = document.importNode(template.content, true); 23 | return clone; 24 | } 25 | 26 | export function showMessage(text = '', title = 'Info', append = false) { 27 | const el = document.getElementById('message-box'); 28 | el.style.visibility = !!text ? 'visible' : 'hidden'; 29 | 30 | let newText = text; 31 | if (append) { 32 | let oldText = getText(el, '.message-body'); 33 | newText = `${oldText}\r\n${text}`; 34 | } 35 | 36 | setText(el, '.message-header', title); 37 | setText(el, '.message-body', newText); 38 | } 39 | 40 | export function showFetching(selector: string) { 41 | const progressClone = cloneElementsFromTemplate('progress-template'); 42 | const heroPlaceholder = document.querySelector(selector); 43 | heroPlaceholder.replaceWith(progressClone); 44 | } 45 | -------------------------------------------------------------------------------- /04-callback/end/src/lib/index.ts: -------------------------------------------------------------------------------- 1 | export * from './data'; 2 | export * from './dom'; 3 | export * from './interfaces'; 4 | export * from './modal'; 5 | export * from './timer'; 6 | -------------------------------------------------------------------------------- /04-callback/end/src/lib/interfaces.ts: -------------------------------------------------------------------------------- 1 | export interface Hero { 2 | id: number; 3 | name: string; 4 | description: string; 5 | email: string; 6 | orders?: Order[]; 7 | accountRep?: AccountRepresentative; 8 | } 9 | 10 | export interface Order { 11 | heroId: number; 12 | num: number; 13 | items: Item[]; 14 | shippingStatus: ShippingStatus; 15 | } 16 | 17 | export interface AccountRepresentative { 18 | repId: number; 19 | name: string; 20 | } 21 | 22 | export interface ShippingStatus { 23 | [index: number]: ShippingStatus; 24 | orderNum: number; 25 | status: string; 26 | } 27 | 28 | export interface Item { 29 | orderNum: number; 30 | name: string; 31 | qty: number; 32 | price: number; 33 | } 34 | 35 | export interface Callback { 36 | (data: T): void; 37 | } 38 | 39 | export interface CallbackError { 40 | (msg?: string): void; 41 | } 42 | -------------------------------------------------------------------------------- /04-callback/end/src/lib/modal.ts: -------------------------------------------------------------------------------- 1 | export const openModal = async function () { 2 | const listenerYes = () => closeModal('yes'); 3 | const listenerNo = () => closeModal('no'); 4 | document.querySelector('.modal-yes').addEventListener('click', listenerYes); 5 | document.querySelector('.modal-no').addEventListener('click', listenerNo); 6 | 7 | const modalWindow = document.getElementById('modal'); 8 | 9 | if (modalWindow.classList) { 10 | modalWindow.classList.add('is-active'); 11 | } 12 | 13 | let resolve: (value?: string) => void; 14 | const responsePromise = new Promise((res, rej) => { 15 | resolve = res; 16 | }); 17 | /** 18 | * This next line returns the promise. 19 | * The caller then waits for the promise to resolve. 20 | */ 21 | return responsePromise; 22 | 23 | function closeModal(yesno: 'yes' | 'no') { 24 | if (modalWindow.classList) { 25 | modalWindow.classList.remove('is-active'); 26 | } 27 | 28 | modalWindow 29 | .querySelector('.modal-yes') 30 | .removeEventListener('click', listenerYes); 31 | modalWindow 32 | .querySelector('.modal-no') 33 | .removeEventListener('click', listenerNo); 34 | 35 | return resolve(yesno); 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /04-callback/end/src/lib/timer.ts: -------------------------------------------------------------------------------- 1 | import { showMessage } from './dom'; 2 | 3 | export const sayHelloTimer = function(ms: number) { 4 | let counter = 0; 5 | showMessage(`Starting the timer`, 'Response from Timer'); 6 | 7 | const callback = (ms: number) => { 8 | counter++; 9 | showMessage( 10 | `Hello every ${ms} milliseconds. (${counter} iterations)`, 11 | 'Response from Timer', 12 | true, 13 | ); 14 | 15 | if (counter === 5) { 16 | showMessage( 17 | `Goodbye. We said hello every ${ms} milliseconds. (after ${counter} iterations)`, 18 | 'Response from Timer', 19 | true, 20 | ); 21 | clearInterval(intervalId); 22 | } 23 | }; 24 | 25 | const intervalId = setInterval(callback, ms, ms); 26 | }; 27 | -------------------------------------------------------------------------------- /04-callback/end/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist/", 4 | "sourceMap": true, 5 | "noImplicitAny": true, 6 | // "module": "es6", 7 | "module": "commonjs", 8 | "target": "es5", 9 | // "allowJs": true, 10 | "allowSyntheticDefaultImports": true, 11 | "lib": ["es2018", "dom"] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /04-callback/end/webpack.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const path = require('path'); 3 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 4 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 5 | 6 | const index = 'index'; 7 | 8 | module.exports = { 9 | entry: `./src/${index}.ts`, 10 | devtool: 'inline-source-map', 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.tsx?$/, 15 | use: 'ts-loader', 16 | exclude: /node_modules/, 17 | }, 18 | { 19 | test: /\.s[ac]ss$/i, 20 | use: [ 21 | MiniCssExtractPlugin.loader, 22 | { 23 | loader: 'css-loader', 24 | }, 25 | { 26 | loader: 'sass-loader', 27 | options: { 28 | sourceMap: true, 29 | }, 30 | }, 31 | ], 32 | }, 33 | ], 34 | }, 35 | plugins: [ 36 | new MiniCssExtractPlugin({ 37 | filename: '[name].bundle.css', 38 | }), 39 | new CopyWebpackPlugin({ 40 | patterns: [ 41 | { 42 | from: `./${index}.html`, 43 | }, 44 | ], 45 | }), 46 | ], 47 | resolve: { 48 | extensions: ['.ts', '.js'], 49 | }, 50 | output: { 51 | filename: 'bundle.js', 52 | path: path.resolve(__dirname, 'dist'), 53 | }, 54 | performance: { 55 | maxEntrypointSize: 640000, 56 | maxAssetSize: 640000, 57 | }, 58 | devServer: { 59 | contentBase: 'dist', 60 | port: 8080, 61 | // Send API requests on localhost to API server get around CORS. 62 | proxy: { 63 | '/api': { 64 | target: { 65 | host: '0.0.0.0', 66 | protocol: 'http:', 67 | port: 8081, 68 | }, 69 | pathRewrite: { 70 | '^/api': '', 71 | }, 72 | }, 73 | }, 74 | }, 75 | }; 76 | -------------------------------------------------------------------------------- /05-promises/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnpapa/typescript-async/bd7bfd359844248efa2b834b438f53dc36aec393/05-promises/README.md -------------------------------------------------------------------------------- /05-promises/begin/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: false, 5 | }, 6 | parser: '@typescript-eslint/parser', // Specifies the ESLint parser 7 | extends: [ 8 | // 'prettier', 9 | // Uses the recommended rules from the @typescript-eslint/eslint-plugin 10 | 'plugin:@typescript-eslint/recommended', 11 | // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier 12 | 'prettier/@typescript-eslint', 13 | // Enables eslint-plugin-prettier and displays prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array. 14 | 'plugin:prettier/recommended', 15 | ], 16 | plugins: ['@typescript-eslint', 'prettier'], 17 | // watch this for explaining why some of this is here 18 | // https://www.youtube.com/watch?time_continue=239&v=YIvjKId9m2c 19 | rules: { 20 | // 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 21 | 'no-console': 'off', 22 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', 23 | '@typescript-eslint/explicit-function-return-type': 'off', 24 | '@typescript-eslint/no-use-before-define': 'off', 25 | quotes: [2, 'single', { avoidEscape: true, allowTemplateLiterals: true }], 26 | 'prettier/prettier': [ 27 | 'error', 28 | { 29 | // trailingComma: 'all', 30 | // singleQuote: true, 31 | // printWidth: 80, 32 | }, 33 | ], 34 | }, 35 | }; 36 | -------------------------------------------------------------------------------- /05-promises/begin/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | /node_modules 10 | node_modules 11 | 12 | # IDEs and editors 13 | /.idea 14 | .project 15 | .classpath 16 | .c9/ 17 | *.launch 18 | .settings/ 19 | *.sublime-workspace 20 | 21 | # IDE - VSCode 22 | .vscode/* 23 | !.vscode/settings.json 24 | !.vscode/tasks.json 25 | !.vscode/launch.json 26 | !.vscode/extensions.json 27 | 28 | # misc 29 | /.sass-cache 30 | /connect.lock 31 | /coverage 32 | /libpeerconnection.log 33 | npm-debug.log 34 | yarn-error.log 35 | testem.log 36 | /typings 37 | 38 | # System Files 39 | .DS_Store 40 | Thumbs.db 41 | -------------------------------------------------------------------------------- /05-promises/begin/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 80, 3 | tabWidth: 2, 4 | bracketSpacing: true, 5 | singleQuote: true, 6 | trailingComma: 'all', 7 | }; 8 | -------------------------------------------------------------------------------- /05-promises/begin/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.enable": true, 3 | "workbench.colorCustomizations": { 4 | "activityBar.background": "#fbed80", 5 | "activityBar.activeBorder": "#06b9a5", 6 | "activityBar.foreground": "#15202b", 7 | "activityBar.inactiveForeground": "#15202b99", 8 | "activityBarBadge.background": "#06b9a5", 9 | "activityBarBadge.foreground": "#15202b", 10 | "titleBar.activeBackground": "#f9e64f", 11 | "titleBar.inactiveBackground": "#f9e64f99", 12 | "titleBar.activeForeground": "#15202b", 13 | "titleBar.inactiveForeground": "#15202b99", 14 | "statusBar.background": "#f9e64f", 15 | "statusBarItem.hoverBackground": "#f7df1e", 16 | "statusBar.foreground": "#15202b", 17 | "panel.border": "#fbed80", 18 | "sideBar.border": "#fbed80", 19 | "editorGroup.border": "#fbed80", 20 | "tab.activeBorder": "#fbed80" 21 | }, 22 | "peacock.color": "#f9e64f", 23 | "typescript.tsdk": "node_modules/typescript/lib" 24 | } -------------------------------------------------------------------------------- /05-promises/begin/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnpapa/typescript-async/bd7bfd359844248efa2b834b438f53dc36aec393/05-promises/begin/README.md -------------------------------------------------------------------------------- /05-promises/begin/compare.md: -------------------------------------------------------------------------------- 1 | # compare files 2 | 3 | ```typescript 4 | // Callback 5 | firstFunction(args1, function() { 6 | secondFunction(args2, function() { 7 | thirdFunction(args3, function() { 8 | // And so on... 9 | }); 10 | }); 11 | }); 12 | 13 | // Promise 14 | function getThings(a) { 15 | return getThing1(a).then(b => getThing2(b)); 16 | then(c => getThing3(c)); 17 | } 18 | 19 | function basicPromise() { 20 | return firstFunction(args1) 21 | .then(args2 => secondFunction(args2)) 22 | .then(args3 => thirdFunction(args3)); 23 | } 24 | 25 | // Async/Await 26 | async function getThings(a) { 27 | const b = await getThing1(a); 28 | const c = await getThing2(b); 29 | return await getThing3(c); 30 | } 31 | 32 | function basicAsyncAwait() { 33 | const args2 = await firstFunction(args1); 34 | const args3 = await secondFunction(args2); 35 | return await thirdFunction(args3); 36 | } 37 | 38 | const getHeroTreeCallback = function(email: string, callback: any) { 39 | getHeroCallback(email, hero => { 40 | getOrdersCallback(hero.id, orders => { 41 | hero.orders = orders; 42 | getAccountRepCallback(hero.id, accountRep => { 43 | hero.accountRep = accountRep; 44 | callback(hero); 45 | }); 46 | }); 47 | }); 48 | }; 49 | 50 | const getHeroTreePromise = function(searchEmail: string) { 51 | let hero: Hero; 52 | return getHeroPromise(searchEmail) 53 | .then((hero: Hero) => Promise.all([getOrders(hero), getAccountRep(hero)])) 54 | .then((result: [Order[], AccountRepresentative]) => { 55 | const [orders, accountRep] = result; 56 | hero.orders = orders; 57 | hero.accountRep = accountRep; 58 | return hero; 59 | }); 60 | }; 61 | 62 | const getHeroTreeAsync = async function(email: string) { 63 | const hero = await getHeroAsync(email); 64 | const [orders, accountRep] = await Promise.all([ 65 | getOrdersAsync(hero), 66 | getAccountRepAsync(hero), 67 | ]); 68 | hero.orders = orders; 69 | hero.accountRep = accountRep; 70 | return hero; 71 | }; 72 | ``` 73 | -------------------------------------------------------------------------------- /05-promises/begin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "heroes", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "concurrently \"npm run backend\" \"webpack-dev-server\"", 7 | "build": "webpack --mode production", 8 | "backend": "json-server --watch db.json --routes routes.json --port 8081 --middlewares ./node_modules/json-server-reset --delay 500" 9 | }, 10 | "dependencies": { 11 | "@fortawesome/fontawesome-svg-core": "^1.2.18", 12 | "@fortawesome/free-solid-svg-icons": "^5.8.2", 13 | "axios": "^0.19.0" 14 | }, 15 | "devDependencies": { 16 | "@typescript-eslint/eslint-plugin": "^2.3.2", 17 | "@typescript-eslint/parser": "^2.3.2", 18 | "bulma": "^0.9.3", 19 | "concurrently": "^4.1.2", 20 | "copy-webpack-plugin": "^6.4.1", 21 | "css-loader": "^3.2.0", 22 | "eslint": "^6.7.0", 23 | "eslint-config-airbnb-base": "^14.0.0", 24 | "eslint-config-prettier": "^6.7.0", 25 | "eslint-plugin-import": "^2.18.2", 26 | "eslint-plugin-prettier": "^3.1.1", 27 | "extract-text-webpack-plugin": "^4.0.0-beta.0", 28 | "json-server": "^0.15.1", 29 | "json-server-reset": "^1.2.0", 30 | "mini-css-extract-plugin": "^0.8.0", 31 | "sass": "^1.44.0", 32 | "prettier": "^1.19.1", 33 | "sass-loader": "^8.0.0", 34 | "style-loader": "^1.0.0", 35 | "ts-loader": "^6.2.0", 36 | "typescript": "^3.7.0-beta", 37 | "webpack": "^4.41.0", 38 | "webpack-cli": "^3.3.9", 39 | "webpack-dev-server": "^3.8.2" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /05-promises/begin/routes.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/*": "/$1", 3 | "/hero": "/heroes", 4 | "/heroes/:email": "/heroes?email=:email", 5 | "/order": "/orders", 6 | "/orders/:heroId": "/orders?heroId=:heroId", 7 | "/accountreps/:heroId": "/accountreps?heroId=:heroId", 8 | "/shippingstatuses/:orderNum": "/shippingstatuses?orderNum=:orderNum", 9 | "/reset": "/reset" 10 | } 11 | -------------------------------------------------------------------------------- /05-promises/begin/src/design/_bulma.scss: -------------------------------------------------------------------------------- 1 | @import 'bulma/bulma.sass'; 2 | 3 | // Using specific Bulma files reduces the 3 production css files from 195kb to 139kb for a 28% decrease 4 | // @import 'bulma/sass/utilities/_all.sass'; 5 | // @import 'bulma/sass/utilities/mixins.sass'; 6 | // // @import 'bulma/sass/utilities/controls.sass'; 7 | // // @import 'bulma/sass/base/generic.sass'; 8 | // // @import 'bulma/sass/base/helpers.sass'; // 277 lines 9 | // // @import 'bulma/sass/base/minireset.sass'; 10 | // // @import 'bulma/sass/layout/footer.sass'; 11 | // // @import 'bulma/sass/layout/section.sass'; // 12 lines 12 | // @import 'bulma/sass/components/card.sass'; 13 | // @import 'bulma/sass/components/list.sass'; // 39 lines 14 | // @import 'bulma/sass/components/menu.sass'; 15 | // // @import 'bulma/sass/components/modal.sass'; 16 | // @import 'bulma/sass/components/navbar.sass'; 17 | // // @import 'bulma/sass/elements/notification.sass'; 18 | // @import 'bulma/sass/elements/button.sass'; 19 | // @import 'bulma/sass/elements/container.sass'; // 25 lines 20 | // @import 'bulma/sass/elements/content.sass'; // 150 lines 21 | // @import 'bulma/sass/elements/title.sass'; // 64 lines 22 | // // // @import 'bulma/sass/elements/icon.sass'; 23 | // // @import 'bulma/sass/form/_all.sass'; 24 | // @import 'bulma/sass/grid/columns.sass'; 25 | -------------------------------------------------------------------------------- /05-promises/begin/src/design/_variables.scss: -------------------------------------------------------------------------------- 1 | $javascript: #f9e64f; 2 | $javascript-light: #fdf190; 3 | $typescript: #4386dd; //294e80 4 | $typescript-light: #4180d3; 5 | $primary: $typescript; 6 | $primary-light: $typescript-light; 7 | $link: $primary; 8 | $info: $primary; 9 | $shade-light: #fafafa; 10 | $accent-color: #00b3e6; 11 | $accent-dark-color: #294e80; 12 | $table-background-color: #b84949; //$shade-light; 13 | -------------------------------------------------------------------------------- /05-promises/begin/src/design/index.scss: -------------------------------------------------------------------------------- 1 | // @charset "utf-8"; 2 | // @import '~bulma/bulma'; 3 | @import 'variables'; 4 | @import 'bulma'; 5 | @import 'styles'; 6 | 7 | // Import a Google Font 8 | @import url('https://fonts.googleapis.com/css?family=Nunito:400,700'); 9 | @import url('https://use.fontawesome.com/releases/v5.4.1/css/all.css'); 10 | -------------------------------------------------------------------------------- /05-promises/begin/src/examples/heroes.ts: -------------------------------------------------------------------------------- 1 | import { Hero } from "../lib"; 2 | 3 | export const heroes: Hero[] = [ 4 | { 5 | id: 10, 6 | name: 'Madelyn', 7 | description: 'the cat whisperer', 8 | email: 'madelyn@acme.com', 9 | }, 10 | { 11 | id: 20, 12 | name: 'Haley', 13 | description: 'pen wielder', 14 | email: 'haley@acme.com', 15 | }, 16 | { 17 | id: 30, 18 | name: 'Ella', 19 | description: 'fashionista', 20 | email: 'ella@acme.com', 21 | }, 22 | { 23 | id: 40, 24 | name: 'Landon', 25 | description: 'arc trooper', 26 | email: 'landon@acme.com', 27 | }, 28 | ]; 29 | -------------------------------------------------------------------------------- /05-promises/begin/src/examples/promise.ts: -------------------------------------------------------------------------------- 1 | import { heroes } from './heroes'; 2 | import { Hero } from '../lib'; 3 | 4 | /** 5 | * Return a fulfilled promise after a given delay. 6 | */ 7 | let delay: () => Promise; 8 | 9 | /** 10 | * Return a fulfilled promise of heroes 11 | */ 12 | let getHeroesDelayedAsync: () => Promise; 13 | 14 | /** 15 | * Return a fulfilled promise of empty array 16 | */ 17 | let getHeroesEmpty: () => Promise<[]>; 18 | 19 | /** 20 | * Get the heroes via a Promise 21 | */ 22 | export let getHeroesViaPromise: () => Promise; 23 | 24 | /** 25 | * Create and return a promise. 26 | * When invoked, it will settle 27 | * by either resolve or reject. 28 | */ 29 | export let getHeroesViaNewPromise: () => Promise; 30 | 31 | /** 32 | * Get the heroes, 33 | * except this always causes a Promise reject 34 | */ 35 | export let getHeroesViaPromiseReject: () => Promise; 36 | 37 | /** 38 | * Get the heroes 39 | * Except this always causes a Promise to reject, too 40 | */ 41 | export let getHeroesViaPromiseRejectShorter: () => Promise; 42 | -------------------------------------------------------------------------------- /05-promises/begin/src/index.ts: -------------------------------------------------------------------------------- 1 | import './design/index.scss'; 2 | 3 | import { getHeroTreePromise, Hero, showFetching, showMessage } from './lib'; 4 | 5 | import { replaceHeroListComponent } from './heroes.component'; 6 | import { 7 | getHeroesViaNewPromise, 8 | getHeroesViaPromise, 9 | getHeroesViaPromiseReject, 10 | getHeroesViaPromiseRejectShorter, 11 | } from './examples/promise'; 12 | 13 | const searchEmailElement = document.getElementById( 14 | 'search-email', 15 | ) as HTMLInputElement; 16 | const button = document.querySelector('.search-button'); 17 | searchEmailElement.addEventListener('keydown', (e: KeyboardEvent) => { 18 | if (e.code === 'Enter') render(); 19 | }); 20 | button.addEventListener('click', render); 21 | 22 | document 23 | .getElementById('resolved-promise') 24 | .addEventListener('click', resolvedPromise); 25 | 26 | document 27 | .getElementById('resolved-using-promise-ctor') 28 | .addEventListener('click', resolvedUsingPromiseConstructor); 29 | 30 | document 31 | .getElementById('rejected-promise') 32 | .addEventListener('click', rejectedPromise); 33 | 34 | document 35 | .getElementById('rejected-promise-shorter') 36 | .addEventListener('click', rejectedPromiseShorter); 37 | 38 | function wrapUp() { 39 | showFetching(false); 40 | } 41 | 42 | function handleErrors(error: any) { 43 | console.error('Oh no! rejected promise!'); 44 | console.error(error); 45 | showMessage(`Something bad happened`, 'Error'); 46 | } 47 | 48 | function showHeroes(heroes: Hero[]) { 49 | console.table(heroes); 50 | showMessage(`Returned ${heroes.length} heroes`); 51 | heroes.forEach(h => showMessage(JSON.stringify(h), 'heroes', true)); 52 | } 53 | 54 | function resolvedPromise() { 55 | showFetching(); 56 | showMessage(); 57 | // TODO - get heroes, with a Promise 58 | } 59 | 60 | function resolvedUsingPromiseConstructor() { 61 | showFetching(); 62 | showMessage(); 63 | // TODO - get heroes, with new Promise 64 | } 65 | 66 | function rejectedPromise() { 67 | showFetching(); 68 | showMessage(); 69 | // TODO - rejected promise 70 | } 71 | 72 | function rejectedPromiseShorter() { 73 | showFetching(); 74 | showMessage(); 75 | // TODO - rejected promise, but shorter 76 | } 77 | 78 | async function render() { 79 | showMessage(); 80 | showFetching(); 81 | /** 82 | * Get all of the hero data. 83 | * Then show the hero with the hero component. 84 | * Handle any errors 85 | * Always turn off the fetching indicator. 86 | */ 87 | // TODO 88 | } 89 | -------------------------------------------------------------------------------- /05-promises/begin/src/lib/data/config.ts: -------------------------------------------------------------------------------- 1 | import { AxiosResponse } from 'axios'; 2 | 3 | export const API = '/api'; 4 | 5 | let apiUrl = API; 6 | 7 | const parseList = (response: AxiosResponse) => { 8 | if (response.status !== 200) throw Error(response.statusText); 9 | if (!response.data) return []; 10 | let list: T[] = response.data; 11 | if (typeof list !== 'object') { 12 | list = []; 13 | } 14 | return list; 15 | }; 16 | 17 | export { apiUrl, parseList }; 18 | -------------------------------------------------------------------------------- /05-promises/begin/src/lib/data/index.ts: -------------------------------------------------------------------------------- 1 | export * from './config'; 2 | export * from './callback'; 3 | export * from './promise'; 4 | -------------------------------------------------------------------------------- /05-promises/begin/src/lib/data/promise.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosResponse, AxiosError } from 'axios'; 2 | 3 | import { Order, Hero, AccountRepresentative } from '../interfaces'; 4 | import { apiUrl, parseList } from './config'; 5 | 6 | /** 7 | * Get the hero and his/her related orders and account rep 8 | * using promises 9 | */ 10 | const getHeroTreePromise = function(searchEmail: string) { 11 | // TODO 12 | }; 13 | 14 | /** 15 | * Get the hero 16 | */ 17 | const getHeroPromise = (email: string) => { 18 | // TODO 19 | }; 20 | 21 | /** 22 | * Get the hero's orders 23 | */ 24 | const getOrdersPromise = function(heroId: number) { 25 | // TODO 26 | }; 27 | 28 | /** 29 | * Get the hero's account rep 30 | */ 31 | const getAccountRepPromise = function(heroId: number) { 32 | // TODO 33 | }; 34 | 35 | export { getHeroTreePromise }; 36 | -------------------------------------------------------------------------------- /05-promises/begin/src/lib/dom.ts: -------------------------------------------------------------------------------- 1 | export function setText( 2 | el: DocumentFragment | HTMLElement, 3 | selector: string, 4 | text: string, 5 | ) { 6 | el.querySelector(selector).textContent = text.toString(); 7 | return el; 8 | } 9 | 10 | export function getText(el: DocumentFragment | HTMLElement, selector: string) { 11 | return el.querySelector(selector).textContent; 12 | } 13 | 14 | export const createDiv = (...classList: string[]) => { 15 | const el = document.createElement('div'); 16 | el.classList.add(...classList); 17 | return el; 18 | }; 19 | 20 | export function cloneElementsFromTemplate(templateName: string) { 21 | const template = document.getElementById(templateName) as HTMLTemplateElement; 22 | const clone = document.importNode(template.content, true); 23 | return clone; 24 | } 25 | 26 | export function showMessage(text = '', title = 'Info', append = false) { 27 | const el = document.getElementById('message-box'); 28 | el.style.visibility = !!text ? 'visible' : 'hidden'; 29 | 30 | let newText = text; 31 | if (append) { 32 | let oldText = getText(el, '.message-body'); 33 | newText = `${oldText}\r\n${text}`; 34 | } 35 | 36 | setText(el, '.message-header', title); 37 | setText(el, '.message-body', newText); 38 | } 39 | 40 | export function showFetching(show: boolean = true) { 41 | if (show) { 42 | clearList(); 43 | } 44 | document.getElementById('progress').style.display = show ? 'block' : 'none'; 45 | } 46 | 47 | export function clearList() { 48 | const heroPlaceholder = document.querySelector('.hero-list'); 49 | const div = createDiv('hero-list'); 50 | div.innerText = ''; 51 | heroPlaceholder.replaceWith(div); 52 | } 53 | -------------------------------------------------------------------------------- /05-promises/begin/src/lib/index.ts: -------------------------------------------------------------------------------- 1 | export * from './data'; 2 | export * from './dom'; 3 | export * from './interfaces'; 4 | export * from './modal'; 5 | export * from './timer'; 6 | -------------------------------------------------------------------------------- /05-promises/begin/src/lib/interfaces.ts: -------------------------------------------------------------------------------- 1 | export interface Hero { 2 | id: number; 3 | name: string; 4 | description: string; 5 | email: string; 6 | orders?: Order[]; 7 | accountRep?: AccountRepresentative; 8 | } 9 | 10 | export interface Order { 11 | heroId: number; 12 | num: number; 13 | items: Item[]; 14 | shippingStatus: ShippingStatus; 15 | } 16 | 17 | export interface AccountRepresentative { 18 | repId: number; 19 | name: string; 20 | } 21 | 22 | export interface ShippingStatus { 23 | [index: number]: ShippingStatus; 24 | orderNum: number; 25 | status: string; 26 | } 27 | 28 | export interface Item { 29 | orderNum: number; 30 | name: string; 31 | qty: number; 32 | price: number; 33 | } 34 | 35 | export interface Callback { 36 | (data: T): void; 37 | } 38 | 39 | export interface CallbackError { 40 | (msg?: string): void; 41 | } 42 | -------------------------------------------------------------------------------- /05-promises/begin/src/lib/modal.ts: -------------------------------------------------------------------------------- 1 | export const openModal = async function () { 2 | const listenerYes = () => closeModal('yes'); 3 | const listenerNo = () => closeModal('no'); 4 | document.querySelector('.modal-yes').addEventListener('click', listenerYes); 5 | document.querySelector('.modal-no').addEventListener('click', listenerNo); 6 | 7 | const modalWindow = document.getElementById('modal'); 8 | 9 | if (modalWindow.classList) { 10 | modalWindow.classList.add('is-active'); 11 | } 12 | 13 | let resolve: (value?: string) => void; 14 | const responsePromise = new Promise((res, rej) => { 15 | resolve = res; 16 | }); 17 | /** 18 | * This next line returns the promise. 19 | * The caller then waits for the promise to resolve. 20 | */ 21 | return responsePromise; 22 | 23 | function closeModal(yesno: 'yes' | 'no') { 24 | if (modalWindow.classList) { 25 | modalWindow.classList.remove('is-active'); 26 | } 27 | 28 | modalWindow 29 | .querySelector('.modal-yes') 30 | .removeEventListener('click', listenerYes); 31 | modalWindow 32 | .querySelector('.modal-no') 33 | .removeEventListener('click', listenerNo); 34 | 35 | return resolve(yesno); 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /05-promises/begin/src/lib/timer.ts: -------------------------------------------------------------------------------- 1 | import { showMessage } from './dom'; 2 | 3 | export const sayHelloTimer = function (ms: number) { 4 | let counter = 0; 5 | showMessage(`Starting the timer`, 'Response from Timer'); 6 | 7 | const callback = (ms: number) => { 8 | counter++; 9 | showMessage( 10 | `Hello every ${ms} milliseconds. (${counter} iterations)`, 11 | 'Response from Timer', 12 | true, 13 | ); 14 | 15 | if (counter === 5) { 16 | showMessage( 17 | `Goodbye. We said hello every ${ms} milliseconds. (after ${counter} iterations)`, 18 | 'Response from Timer', 19 | true, 20 | ); 21 | clearInterval(intervalId); 22 | } 23 | }; 24 | 25 | const intervalId = setInterval(callback, ms, ms); 26 | }; 27 | -------------------------------------------------------------------------------- /05-promises/begin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist/", 4 | "sourceMap": true, 5 | "noImplicitAny": true, 6 | // "module": "es6", 7 | "module": "commonjs", 8 | "target": "es5", 9 | // "allowJs": true, 10 | "allowSyntheticDefaultImports": true, 11 | "lib": ["es2018", "dom"] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /05-promises/begin/webpack.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const path = require('path'); 3 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 4 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 5 | 6 | const index = 'index'; 7 | 8 | module.exports = { 9 | entry: `./src/${index}.ts`, 10 | devtool: 'inline-source-map', 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.tsx?$/, 15 | use: 'ts-loader', 16 | exclude: /node_modules/, 17 | }, 18 | { 19 | test: /\.s[ac]ss$/i, 20 | use: [ 21 | MiniCssExtractPlugin.loader, 22 | { 23 | loader: 'css-loader', 24 | }, 25 | { 26 | loader: 'sass-loader', 27 | options: { 28 | sourceMap: true, 29 | }, 30 | }, 31 | ], 32 | }, 33 | ], 34 | }, 35 | plugins: [ 36 | new MiniCssExtractPlugin({ 37 | filename: '[name].bundle.css', 38 | }), 39 | new CopyWebpackPlugin({ 40 | patterns: [ 41 | { 42 | from: `./${index}.html`, 43 | }, 44 | ], 45 | }), 46 | ], 47 | resolve: { 48 | extensions: ['.ts', '.js'], 49 | }, 50 | output: { 51 | filename: 'bundle.js', 52 | path: path.resolve(__dirname, 'dist'), 53 | }, 54 | performance: { 55 | maxEntrypointSize: 640000, 56 | maxAssetSize: 640000, 57 | }, 58 | devServer: { 59 | contentBase: 'dist', 60 | port: 8080, 61 | // Send API requests on localhost to API server get around CORS. 62 | proxy: { 63 | '/api': { 64 | target: { 65 | host: '0.0.0.0', 66 | protocol: 'http:', 67 | port: 8081, 68 | }, 69 | pathRewrite: { 70 | '^/api': '', 71 | }, 72 | }, 73 | }, 74 | }, 75 | }; 76 | -------------------------------------------------------------------------------- /05-promises/end/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: false, 5 | }, 6 | parser: '@typescript-eslint/parser', // Specifies the ESLint parser 7 | extends: [ 8 | // 'prettier', 9 | // Uses the recommended rules from the @typescript-eslint/eslint-plugin 10 | 'plugin:@typescript-eslint/recommended', 11 | // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier 12 | 'prettier/@typescript-eslint', 13 | // Enables eslint-plugin-prettier and displays prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array. 14 | 'plugin:prettier/recommended', 15 | ], 16 | plugins: ['@typescript-eslint', 'prettier'], 17 | // watch this for explaining why some of this is here 18 | // https://www.youtube.com/watch?time_continue=239&v=YIvjKId9m2c 19 | rules: { 20 | // 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 21 | 'no-console': 'off', 22 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', 23 | '@typescript-eslint/explicit-function-return-type': 'off', 24 | '@typescript-eslint/no-use-before-define': 'off', 25 | quotes: [2, 'single', { avoidEscape: true, allowTemplateLiterals: true }], 26 | 'prettier/prettier': [ 27 | 'error', 28 | { 29 | // trailingComma: 'all', 30 | // singleQuote: true, 31 | // printWidth: 80, 32 | }, 33 | ], 34 | }, 35 | }; 36 | -------------------------------------------------------------------------------- /05-promises/end/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | /node_modules 10 | node_modules 11 | 12 | # IDEs and editors 13 | /.idea 14 | .project 15 | .classpath 16 | .c9/ 17 | *.launch 18 | .settings/ 19 | *.sublime-workspace 20 | 21 | # IDE - VSCode 22 | .vscode/* 23 | !.vscode/settings.json 24 | !.vscode/tasks.json 25 | !.vscode/launch.json 26 | !.vscode/extensions.json 27 | 28 | # misc 29 | /.sass-cache 30 | /connect.lock 31 | /coverage 32 | /libpeerconnection.log 33 | npm-debug.log 34 | yarn-error.log 35 | testem.log 36 | /typings 37 | 38 | # System Files 39 | .DS_Store 40 | Thumbs.db 41 | -------------------------------------------------------------------------------- /05-promises/end/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 80, 3 | tabWidth: 2, 4 | bracketSpacing: true, 5 | singleQuote: true, 6 | trailingComma: 'all', 7 | }; 8 | -------------------------------------------------------------------------------- /05-promises/end/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.enable": true, 3 | "workbench.colorCustomizations": { 4 | "activityBar.background": "#fbed80", 5 | "activityBar.activeBorder": "#06b9a5", 6 | "activityBar.foreground": "#15202b", 7 | "activityBar.inactiveForeground": "#15202b99", 8 | "activityBarBadge.background": "#06b9a5", 9 | "activityBarBadge.foreground": "#15202b", 10 | "titleBar.activeBackground": "#f9e64f", 11 | "titleBar.inactiveBackground": "#f9e64f99", 12 | "titleBar.activeForeground": "#15202b", 13 | "titleBar.inactiveForeground": "#15202b99", 14 | "statusBar.background": "#f9e64f", 15 | "statusBarItem.hoverBackground": "#f7df1e", 16 | "statusBar.foreground": "#15202b", 17 | "panel.border": "#fbed80", 18 | "sideBar.border": "#fbed80", 19 | "editorGroup.border": "#fbed80", 20 | "tab.activeBorder": "#fbed80" 21 | }, 22 | "peacock.color": "#f9e64f", 23 | "typescript.tsdk": "node_modules/typescript/lib" 24 | } -------------------------------------------------------------------------------- /05-promises/end/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnpapa/typescript-async/bd7bfd359844248efa2b834b438f53dc36aec393/05-promises/end/README.md -------------------------------------------------------------------------------- /05-promises/end/compare.md: -------------------------------------------------------------------------------- 1 | # compare files 2 | 3 | ```typescript 4 | // Callback 5 | firstFunction(args1, function() { 6 | secondFunction(args2, function() { 7 | thirdFunction(args3, function() { 8 | // And so on... 9 | }); 10 | }); 11 | }); 12 | 13 | // Promise 14 | function getThings(a) { 15 | return getThing1(a).then(b => getThing2(b)); 16 | then(c => getThing3(c)); 17 | } 18 | 19 | function basicPromise() { 20 | return firstFunction(args1) 21 | .then(args2 => secondFunction(args2)) 22 | .then(args3 => thirdFunction(args3)); 23 | } 24 | 25 | // Async/Await 26 | async function getThings(a) { 27 | const b = await getThing1(a); 28 | const c = await getThing2(b); 29 | return await getThing3(c); 30 | } 31 | 32 | function basicAsyncAwait() { 33 | const args2 = await firstFunction(args1); 34 | const args3 = await secondFunction(args2); 35 | return await thirdFunction(args3); 36 | } 37 | 38 | const getHeroTreeCallback = function(email: string, callback: any) { 39 | getHeroCallback(email, hero => { 40 | getOrdersCallback(hero.id, orders => { 41 | hero.orders = orders; 42 | getAccountRepCallback(hero.id, accountRep => { 43 | hero.accountRep = accountRep; 44 | callback(hero); 45 | }); 46 | }); 47 | }); 48 | }; 49 | 50 | const getHeroTreePromise = function(searchEmail: string) { 51 | let hero: Hero; 52 | return getHeroPromise(searchEmail) 53 | .then((hero: Hero) => Promise.all([getOrders(hero), getAccountRep(hero)])) 54 | .then((result: [Order[], AccountRepresentative]) => { 55 | const [orders, accountRep] = result; 56 | hero.orders = orders; 57 | hero.accountRep = accountRep; 58 | return hero; 59 | }); 60 | }; 61 | 62 | const getHeroTreeAsync = async function(email: string) { 63 | const hero = await getHeroAsync(email); 64 | const [orders, accountRep] = await Promise.all([ 65 | getOrdersAsync(hero), 66 | getAccountRepAsync(hero), 67 | ]); 68 | hero.orders = orders; 69 | hero.accountRep = accountRep; 70 | return hero; 71 | }; 72 | ``` 73 | -------------------------------------------------------------------------------- /05-promises/end/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "heroes", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "concurrently \"npm run backend\" \"webpack-dev-server\"", 7 | "build": "webpack --mode production", 8 | "backend": "json-server --watch db.json --routes routes.json --port 8081 --middlewares ./node_modules/json-server-reset --delay 500" 9 | }, 10 | "dependencies": { 11 | "@fortawesome/fontawesome-svg-core": "^1.2.18", 12 | "@fortawesome/free-solid-svg-icons": "^5.8.2", 13 | "axios": "^0.19.0" 14 | }, 15 | "devDependencies": { 16 | "@typescript-eslint/eslint-plugin": "^2.3.2", 17 | "@typescript-eslint/parser": "^2.3.2", 18 | "bulma": "^0.9.3", 19 | "concurrently": "^4.1.2", 20 | "copy-webpack-plugin": "^6.4.1", 21 | "css-loader": "^3.2.0", 22 | "eslint": "^6.7.0", 23 | "eslint-config-airbnb-base": "^14.0.0", 24 | "eslint-config-prettier": "^6.7.0", 25 | "eslint-plugin-import": "^2.18.2", 26 | "eslint-plugin-prettier": "^3.1.1", 27 | "extract-text-webpack-plugin": "^4.0.0-beta.0", 28 | "json-server": "^0.15.1", 29 | "json-server-reset": "^1.2.0", 30 | "mini-css-extract-plugin": "^0.8.0", 31 | "sass": "^1.44.0", 32 | "prettier": "^1.19.1", 33 | "sass-loader": "^8.0.0", 34 | "style-loader": "^1.0.0", 35 | "ts-loader": "^6.2.0", 36 | "typescript": "^3.7.0-beta", 37 | "webpack": "^4.41.0", 38 | "webpack-cli": "^3.3.9", 39 | "webpack-dev-server": "^3.8.2" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /05-promises/end/routes.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/*": "/$1", 3 | "/hero": "/heroes", 4 | "/heroes/:email": "/heroes?email=:email", 5 | "/order": "/orders", 6 | "/orders/:heroId": "/orders?heroId=:heroId", 7 | "/accountreps/:heroId": "/accountreps?heroId=:heroId", 8 | "/shippingstatuses/:orderNum": "/shippingstatuses?orderNum=:orderNum", 9 | "/reset": "/reset" 10 | } 11 | -------------------------------------------------------------------------------- /05-promises/end/src/design/_bulma.scss: -------------------------------------------------------------------------------- 1 | @import 'bulma/bulma.sass'; 2 | 3 | // Using specific Bulma files reduces the 3 production css files from 195kb to 139kb for a 28% decrease 4 | // @import 'bulma/sass/utilities/_all.sass'; 5 | // @import 'bulma/sass/utilities/mixins.sass'; 6 | // // @import 'bulma/sass/utilities/controls.sass'; 7 | // // @import 'bulma/sass/base/generic.sass'; 8 | // // @import 'bulma/sass/base/helpers.sass'; // 277 lines 9 | // // @import 'bulma/sass/base/minireset.sass'; 10 | // // @import 'bulma/sass/layout/footer.sass'; 11 | // // @import 'bulma/sass/layout/section.sass'; // 12 lines 12 | // @import 'bulma/sass/components/card.sass'; 13 | // @import 'bulma/sass/components/list.sass'; // 39 lines 14 | // @import 'bulma/sass/components/menu.sass'; 15 | // // @import 'bulma/sass/components/modal.sass'; 16 | // @import 'bulma/sass/components/navbar.sass'; 17 | // // @import 'bulma/sass/elements/notification.sass'; 18 | // @import 'bulma/sass/elements/button.sass'; 19 | // @import 'bulma/sass/elements/container.sass'; // 25 lines 20 | // @import 'bulma/sass/elements/content.sass'; // 150 lines 21 | // @import 'bulma/sass/elements/title.sass'; // 64 lines 22 | // // // @import 'bulma/sass/elements/icon.sass'; 23 | // // @import 'bulma/sass/form/_all.sass'; 24 | // @import 'bulma/sass/grid/columns.sass'; 25 | -------------------------------------------------------------------------------- /05-promises/end/src/design/_variables.scss: -------------------------------------------------------------------------------- 1 | $javascript: #f9e64f; 2 | $javascript-light: #fdf190; 3 | $typescript: #4386dd; //294e80 4 | $typescript-light: #4180d3; 5 | $primary: $typescript; 6 | $primary-light: $typescript-light; 7 | $link: $primary; 8 | $info: $primary; 9 | $shade-light: #fafafa; 10 | $accent-color: #00b3e6; 11 | $accent-dark-color: #294e80; 12 | $table-background-color: #b84949; //$shade-light; 13 | -------------------------------------------------------------------------------- /05-promises/end/src/design/index.scss: -------------------------------------------------------------------------------- 1 | // @charset "utf-8"; 2 | // @import '~bulma/bulma'; 3 | @import 'variables'; 4 | @import 'bulma'; 5 | @import 'styles'; 6 | 7 | // Import a Google Font 8 | @import url('https://fonts.googleapis.com/css?family=Nunito:400,700'); 9 | @import url('https://use.fontawesome.com/releases/v5.4.1/css/all.css'); 10 | -------------------------------------------------------------------------------- /05-promises/end/src/examples/heroes.ts: -------------------------------------------------------------------------------- 1 | import { Hero } from "../lib"; 2 | 3 | export const heroes: Hero[] = [ 4 | { 5 | id: 10, 6 | name: 'Madelyn', 7 | description: 'the cat whisperer', 8 | email: 'madelyn@acme.com', 9 | }, 10 | { 11 | id: 20, 12 | name: 'Haley', 13 | description: 'pen wielder', 14 | email: 'haley@acme.com', 15 | }, 16 | { 17 | id: 30, 18 | name: 'Ella', 19 | description: 'fashionista', 20 | email: 'ella@acme.com', 21 | }, 22 | { 23 | id: 40, 24 | name: 'Landon', 25 | description: 'arc trooper', 26 | email: 'landon@acme.com', 27 | }, 28 | ]; 29 | -------------------------------------------------------------------------------- /05-promises/end/src/examples/promise.ts: -------------------------------------------------------------------------------- 1 | import { heroes } from './heroes'; 2 | import { Hero } from '../lib'; 3 | 4 | /** 5 | * Return a fulfilled promise after a given delay. 6 | */ 7 | const delay = (ms: number) => 8 | new Promise(resolve => setTimeout(resolve, ms)); 9 | 10 | /** 11 | * Return a fulfilled promise of heroes 12 | */ 13 | const getHeroesDelayedAsync = () => 14 | new Promise(resolve => resolve(heroes)); 15 | 16 | /** 17 | * Return a fulfilled promise of empty array 18 | */ 19 | const getHeroesEmpty = () => Promise.resolve([]); 20 | 21 | /** 22 | * Get the heroes via a Promise 23 | */ 24 | export const getHeroesViaPromise = function() { 25 | return delay(1000).then(() => getHeroesDelayedAsync()); 26 | }; 27 | 28 | /** 29 | * Create and return a promise. 30 | * When invoked, it will settle 31 | * by either resolve or reject. 32 | */ 33 | export function getHeroesViaNewPromise() { 34 | const newPromise = new Promise((resolve, reject) => { 35 | return delay(1000) 36 | .then(() => getHeroesDelayedAsync()) 37 | .then((heroes: Hero[]) => { 38 | if (heroes && heroes.length) { 39 | resolve(heroes); 40 | } else { 41 | reject(Error('Uh oh! Errors!')); 42 | } 43 | }); 44 | }); 45 | return newPromise; 46 | } 47 | 48 | /** 49 | * Get the heroes, 50 | * except this always causes a Promise to reject 51 | */ 52 | export const getHeroesViaPromiseReject = function() { 53 | const newPromise = new Promise((resolve, reject) => { 54 | return delay(1000) 55 | .then(() => getHeroesEmpty()) 56 | .then((heroes: Hero[]) => { 57 | if (heroes && heroes.length) { 58 | resolve(heroes); 59 | } else { 60 | reject(Error('Uh oh! Errors!')); 61 | } 62 | }); 63 | }); 64 | return newPromise; 65 | }; 66 | 67 | /** 68 | * Get the heroes 69 | * Except this always causes a Promise to reject, too 70 | */ 71 | export const getHeroesViaPromiseRejectShorter: () => Promise< 72 | Hero[] 73 | > = function() { 74 | const getsHeroesOrDoesIt = () => 75 | Promise.reject('bad error occurred getting the heroes'); 76 | 77 | return delay(1000).then(() => getsHeroesOrDoesIt()); 78 | }; 79 | 80 | // function example() { 81 | // showFetching(true); 82 | 83 | // return getHeroes() 84 | // .then((hero: Hero) => replaceHeroListComponent(hero)) 85 | // .catch((error: any) => { 86 | // showMessage(error); 87 | // }) 88 | // .finally(() => { 89 | // showFetching(false); 90 | // }); 91 | // } 92 | -------------------------------------------------------------------------------- /05-promises/end/src/lib/data/config.ts: -------------------------------------------------------------------------------- 1 | import { AxiosResponse } from 'axios'; 2 | 3 | export const API = '/api'; 4 | 5 | let apiUrl = API; 6 | 7 | const parseList = (response: AxiosResponse) => { 8 | if (response.status !== 200) throw Error(response.statusText); 9 | if (!response.data) return []; 10 | let list: T[] = response.data; 11 | if (typeof list !== 'object') { 12 | list = []; 13 | } 14 | return list; 15 | }; 16 | 17 | export { apiUrl, parseList }; 18 | -------------------------------------------------------------------------------- /05-promises/end/src/lib/data/index.ts: -------------------------------------------------------------------------------- 1 | export * from './config'; 2 | export * from './callback'; 3 | export * from './promise'; 4 | -------------------------------------------------------------------------------- /05-promises/end/src/lib/data/promise.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosResponse, AxiosError } from 'axios'; 2 | 3 | import { Order, Hero, AccountRepresentative } from '../interfaces'; 4 | import { apiUrl, parseList } from './config'; 5 | 6 | /** 7 | * Get the hero and his/her related orders and account rep 8 | * using promises 9 | */ 10 | const getHeroTreePromise = function(searchEmail: string) { 11 | let hero: Hero; 12 | 13 | return getHeroPromise(searchEmail) 14 | .then((h: Hero) => { 15 | hero = h; 16 | return h; 17 | }) 18 | .then((hero: Hero) => 19 | Promise.all([getOrdersPromise(hero.id), getAccountRepPromise(hero.id)]), 20 | ) 21 | .then((result: [Order[], AccountRepresentative]) => mergeData(result)); 22 | 23 | function mergeData(result: [Order[], AccountRepresentative]): Hero { 24 | const [orders, accountRep] = result; 25 | if (orders) { 26 | hero.orders = orders; 27 | } 28 | if (accountRep) { 29 | hero.accountRep = accountRep; 30 | } 31 | return hero; 32 | } 33 | }; 34 | 35 | /** 36 | * Get the hero 37 | */ 38 | const getHeroPromise = (email: string) => { 39 | return axios 40 | .get(`${apiUrl}/heroes?email=${email}`) 41 | .then((response: AxiosResponse) => { 42 | const data = parseList(response); 43 | const hero = data[0]; 44 | return hero; 45 | }) 46 | .catch((error: AxiosError) => handleAxiosErrors(error, 'Hero')); 47 | }; 48 | 49 | /** 50 | * Get the hero's orders 51 | */ 52 | const getOrdersPromise = function(heroId: number) { 53 | return axios 54 | .get(`${apiUrl}/orders/${heroId}`) 55 | .then((response: AxiosResponse) => parseList(response)) 56 | .catch((error: AxiosError) => handleAxiosErrors(error, 'Orders')); 57 | }; 58 | 59 | /** 60 | * Get the hero's account rep 61 | */ 62 | const getAccountRepPromise = function(heroId: number) { 63 | return axios 64 | .get(`${apiUrl}/accountreps/${heroId}`) 65 | .then((response: AxiosResponse) => { 66 | const data = parseList(response); 67 | return data[0]; 68 | }) 69 | .catch((error: AxiosError) => handleAxiosErrors(error, 'Account Reps')); 70 | }; 71 | 72 | function handleAxiosErrors(error: AxiosError, model: string) { 73 | console.error(`Developer Error: Async Data Error: ${error.message}`); 74 | return Promise.reject(`Oh no! We're unable to fetch the ${model}`); 75 | 76 | /** 77 | * Option - you could throw an error, 78 | * 79 | * but it won't be caught if you are inside 80 | * of another async callback. 81 | * So it is safer to reject. 82 | * throw new Error('User Facing Error: Something bad happened'); 83 | */ 84 | } 85 | 86 | export { getHeroTreePromise }; 87 | -------------------------------------------------------------------------------- /05-promises/end/src/lib/dom.ts: -------------------------------------------------------------------------------- 1 | export function setText( 2 | el: DocumentFragment | HTMLElement, 3 | selector: string, 4 | text: string, 5 | ) { 6 | el.querySelector(selector).textContent = text.toString(); 7 | return el; 8 | } 9 | 10 | export function getText(el: DocumentFragment | HTMLElement, selector: string) { 11 | return el.querySelector(selector).textContent; 12 | } 13 | 14 | export const createDiv = (...classList: string[]) => { 15 | const el = document.createElement('div'); 16 | el.classList.add(...classList); 17 | return el; 18 | }; 19 | 20 | export function cloneElementsFromTemplate(templateName: string) { 21 | const template = document.getElementById(templateName) as HTMLTemplateElement; 22 | const clone = document.importNode(template.content, true); 23 | return clone; 24 | } 25 | 26 | export function showMessage(text = '', title = 'Info', append = false) { 27 | const el = document.getElementById('message-box'); 28 | el.style.visibility = !!text ? 'visible' : 'hidden'; 29 | 30 | let newText = text; 31 | if (append) { 32 | let oldText = getText(el, '.message-body'); 33 | newText = `${oldText}\r\n${text}`; 34 | } 35 | 36 | setText(el, '.message-header', title); 37 | setText(el, '.message-body', newText); 38 | } 39 | 40 | export function showFetching(show: boolean = true) { 41 | if (show) { 42 | clearList(); 43 | } 44 | document.getElementById('progress').style.display = show ? 'block' : 'none'; 45 | } 46 | 47 | export function clearList() { 48 | const heroPlaceholder = document.querySelector('.hero-list'); 49 | const div = createDiv('hero-list'); 50 | div.innerText = ''; 51 | heroPlaceholder.replaceWith(div); 52 | } 53 | -------------------------------------------------------------------------------- /05-promises/end/src/lib/index.ts: -------------------------------------------------------------------------------- 1 | export * from './data'; 2 | export * from './dom'; 3 | export * from './interfaces'; 4 | export * from './modal'; 5 | export * from './timer'; 6 | -------------------------------------------------------------------------------- /05-promises/end/src/lib/interfaces.ts: -------------------------------------------------------------------------------- 1 | export interface Hero { 2 | id: number; 3 | name: string; 4 | description: string; 5 | email: string; 6 | orders?: Order[]; 7 | accountRep?: AccountRepresentative; 8 | } 9 | 10 | export interface Order { 11 | heroId: number; 12 | num: number; 13 | items: Item[]; 14 | shippingStatus: ShippingStatus; 15 | } 16 | 17 | export interface AccountRepresentative { 18 | repId: number; 19 | name: string; 20 | } 21 | 22 | export interface ShippingStatus { 23 | [index: number]: ShippingStatus; 24 | orderNum: number; 25 | status: string; 26 | } 27 | 28 | export interface Item { 29 | orderNum: number; 30 | name: string; 31 | qty: number; 32 | price: number; 33 | } 34 | 35 | export interface Callback { 36 | (data: T): void; 37 | } 38 | 39 | export interface CallbackError { 40 | (msg?: string): void; 41 | } 42 | -------------------------------------------------------------------------------- /05-promises/end/src/lib/modal.ts: -------------------------------------------------------------------------------- 1 | export const openModal = async function () { 2 | const listenerYes = () => closeModal('yes'); 3 | const listenerNo = () => closeModal('no'); 4 | document.querySelector('.modal-yes').addEventListener('click', listenerYes); 5 | document.querySelector('.modal-no').addEventListener('click', listenerNo); 6 | 7 | const modalWindow = document.getElementById('modal'); 8 | 9 | if (modalWindow.classList) { 10 | modalWindow.classList.add('is-active'); 11 | } 12 | 13 | let resolve: (value?: string) => void; 14 | const responsePromise = new Promise((res, rej) => { 15 | resolve = res; 16 | }); 17 | /** 18 | * This next line returns the promise. 19 | * The caller then waits for the promise to resolve. 20 | */ 21 | return responsePromise; 22 | 23 | function closeModal(yesno: 'yes' | 'no') { 24 | if (modalWindow.classList) { 25 | modalWindow.classList.remove('is-active'); 26 | } 27 | 28 | modalWindow 29 | .querySelector('.modal-yes') 30 | .removeEventListener('click', listenerYes); 31 | modalWindow 32 | .querySelector('.modal-no') 33 | .removeEventListener('click', listenerNo); 34 | 35 | return resolve(yesno); 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /05-promises/end/src/lib/timer.ts: -------------------------------------------------------------------------------- 1 | import { showMessage } from './dom'; 2 | 3 | export const sayHelloTimer = function (ms: number) { 4 | let counter = 0; 5 | showMessage(`Starting the timer`, 'Response from Timer'); 6 | 7 | const callback = (ms: number) => { 8 | counter++; 9 | showMessage( 10 | `Hello every ${ms} milliseconds. (${counter} iterations)`, 11 | 'Response from Timer', 12 | true, 13 | ); 14 | 15 | if (counter === 5) { 16 | showMessage( 17 | `Goodbye. We said hello every ${ms} milliseconds. (after ${counter} iterations)`, 18 | 'Response from Timer', 19 | true, 20 | ); 21 | clearInterval(intervalId); 22 | } 23 | }; 24 | 25 | const intervalId = setInterval(callback, ms, ms); 26 | }; 27 | -------------------------------------------------------------------------------- /05-promises/end/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist/", 4 | "sourceMap": true, 5 | "noImplicitAny": true, 6 | // "module": "es6", 7 | "module": "commonjs", 8 | "target": "es5", 9 | // "allowJs": true, 10 | "allowSyntheticDefaultImports": true, 11 | "lib": ["es2018", "dom"] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /05-promises/end/webpack.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const path = require('path'); 3 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 4 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 5 | 6 | const index = 'index'; 7 | 8 | module.exports = { 9 | entry: `./src/${index}.ts`, 10 | devtool: 'inline-source-map', 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.tsx?$/, 15 | use: 'ts-loader', 16 | exclude: /node_modules/, 17 | }, 18 | { 19 | test: /\.s[ac]ss$/i, 20 | use: [ 21 | MiniCssExtractPlugin.loader, 22 | { 23 | loader: 'css-loader', 24 | }, 25 | { 26 | loader: 'sass-loader', 27 | options: { 28 | sourceMap: true, 29 | }, 30 | }, 31 | ], 32 | }, 33 | ], 34 | }, 35 | plugins: [ 36 | new MiniCssExtractPlugin({ 37 | filename: '[name].bundle.css', 38 | }), 39 | new CopyWebpackPlugin({ 40 | patterns: [ 41 | { 42 | from: `./${index}.html`, 43 | }, 44 | ], 45 | }), 46 | ], 47 | resolve: { 48 | extensions: ['.ts', '.js'], 49 | }, 50 | output: { 51 | filename: 'bundle.js', 52 | path: path.resolve(__dirname, 'dist'), 53 | }, 54 | performance: { 55 | maxEntrypointSize: 640000, 56 | maxAssetSize: 640000, 57 | }, 58 | devServer: { 59 | contentBase: 'dist', 60 | port: 8080, 61 | // Send API requests on localhost to API server get around CORS. 62 | proxy: { 63 | '/api': { 64 | target: { 65 | host: '0.0.0.0', 66 | protocol: 'http:', 67 | port: 8081, 68 | }, 69 | pathRewrite: { 70 | '^/api': '', 71 | }, 72 | }, 73 | }, 74 | }, 75 | }; 76 | -------------------------------------------------------------------------------- /06-async-await/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnpapa/typescript-async/bd7bfd359844248efa2b834b438f53dc36aec393/06-async-await/README.md -------------------------------------------------------------------------------- /06-async-await/begin/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: false, 5 | }, 6 | parser: '@typescript-eslint/parser', // Specifies the ESLint parser 7 | extends: [ 8 | // 'prettier', 9 | // Uses the recommended rules from the @typescript-eslint/eslint-plugin 10 | 'plugin:@typescript-eslint/recommended', 11 | // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier 12 | 'prettier/@typescript-eslint', 13 | // Enables eslint-plugin-prettier and displays prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array. 14 | 'plugin:prettier/recommended', 15 | ], 16 | plugins: ['@typescript-eslint', 'prettier'], 17 | // watch this for explaining why some of this is here 18 | // https://www.youtube.com/watch?time_continue=239&v=YIvjKId9m2c 19 | rules: { 20 | // 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 21 | 'no-console': 'off', 22 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', 23 | '@typescript-eslint/explicit-function-return-type': 'off', 24 | '@typescript-eslint/no-use-before-define': 'off', 25 | quotes: [2, 'single', { avoidEscape: true, allowTemplateLiterals: true }], 26 | 'prettier/prettier': [ 27 | 'error', 28 | { 29 | // trailingComma: 'all', 30 | // singleQuote: true, 31 | // printWidth: 80, 32 | }, 33 | ], 34 | }, 35 | }; 36 | -------------------------------------------------------------------------------- /06-async-await/begin/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | /node_modules 10 | node_modules 11 | 12 | # IDEs and editors 13 | /.idea 14 | .project 15 | .classpath 16 | .c9/ 17 | *.launch 18 | .settings/ 19 | *.sublime-workspace 20 | 21 | # IDE - VSCode 22 | .vscode/* 23 | !.vscode/settings.json 24 | !.vscode/tasks.json 25 | !.vscode/launch.json 26 | !.vscode/extensions.json 27 | 28 | # misc 29 | /.sass-cache 30 | /connect.lock 31 | /coverage 32 | /libpeerconnection.log 33 | npm-debug.log 34 | yarn-error.log 35 | testem.log 36 | /typings 37 | 38 | # System Files 39 | .DS_Store 40 | Thumbs.db 41 | -------------------------------------------------------------------------------- /06-async-await/begin/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 80, 3 | tabWidth: 2, 4 | bracketSpacing: true, 5 | singleQuote: true, 6 | trailingComma: 'all', 7 | }; 8 | -------------------------------------------------------------------------------- /06-async-await/begin/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.enable": true, 3 | "workbench.colorCustomizations": { 4 | "activityBar.background": "#fbed80", 5 | "activityBar.activeBorder": "#06b9a5", 6 | "activityBar.foreground": "#15202b", 7 | "activityBar.inactiveForeground": "#15202b99", 8 | "activityBarBadge.background": "#06b9a5", 9 | "activityBarBadge.foreground": "#15202b", 10 | "titleBar.activeBackground": "#f9e64f", 11 | "titleBar.inactiveBackground": "#f9e64f99", 12 | "titleBar.activeForeground": "#15202b", 13 | "titleBar.inactiveForeground": "#15202b99", 14 | "statusBar.background": "#f9e64f", 15 | "statusBarItem.hoverBackground": "#f7df1e", 16 | "statusBar.foreground": "#15202b", 17 | "panel.border": "#fbed80", 18 | "sideBar.border": "#fbed80", 19 | "editorGroup.border": "#fbed80", 20 | "tab.activeBorder": "#fbed80" 21 | }, 22 | "peacock.color": "#f9e64f", 23 | "typescript.tsdk": "node_modules/typescript/lib" 24 | } -------------------------------------------------------------------------------- /06-async-await/begin/README copy.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnpapa/typescript-async/bd7bfd359844248efa2b834b438f53dc36aec393/06-async-await/begin/README copy.md -------------------------------------------------------------------------------- /06-async-await/begin/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnpapa/typescript-async/bd7bfd359844248efa2b834b438f53dc36aec393/06-async-await/begin/README.md -------------------------------------------------------------------------------- /06-async-await/begin/compare.md: -------------------------------------------------------------------------------- 1 | # compare files 2 | 3 | ```typescript 4 | // Callback 5 | firstFunction(args1, function() { 6 | secondFunction(args2, function() { 7 | thirdFunction(args3, function() { 8 | // And so on... 9 | }); 10 | }); 11 | }); 12 | 13 | // Promise 14 | function getThings(a) { 15 | return getThing1(a).then(b => getThing2(b)); 16 | then(c => getThing3(c)); 17 | } 18 | 19 | function basicPromise() { 20 | return firstFunction(args1) 21 | .then(args2 => secondFunction(args2)) 22 | .then(args3 => thirdFunction(args3)); 23 | } 24 | 25 | // Async/Await 26 | async function getThings(a) { 27 | const b = await getThing1(a); 28 | const c = await getThing2(b); 29 | return await getThing3(c); 30 | } 31 | 32 | function basicAsyncAwait() { 33 | const args2 = await firstFunction(args1); 34 | const args3 = await secondFunction(args2); 35 | return await thirdFunction(args3); 36 | } 37 | 38 | const getHeroTreeCallback = function(email: string, callback: any) { 39 | getHeroCallback(email, hero => { 40 | getOrdersCallback(hero.id, orders => { 41 | hero.orders = orders; 42 | getAccountRepCallback(hero.id, accountRep => { 43 | hero.accountRep = accountRep; 44 | callback(hero); 45 | }); 46 | }); 47 | }); 48 | }; 49 | 50 | const getHeroTreePromise = function(searchEmail: string) { 51 | let hero: Hero; 52 | return getHeroPromise(searchEmail) 53 | .then((hero: Hero) => Promise.all([getOrders(hero), getAccountRep(hero)])) 54 | .then((result: [Order[], AccountRepresentative]) => { 55 | const [orders, accountRep] = result; 56 | hero.orders = orders; 57 | hero.accountRep = accountRep; 58 | return hero; 59 | }); 60 | }; 61 | 62 | const getHeroTreeAsync = async function(email: string) { 63 | const hero = await getHeroAsync(email); 64 | const [orders, accountRep] = await Promise.all([ 65 | getOrdersAsync(hero), 66 | getAccountRepAsync(hero), 67 | ]); 68 | hero.orders = orders; 69 | hero.accountRep = accountRep; 70 | return hero; 71 | }; 72 | ``` 73 | -------------------------------------------------------------------------------- /06-async-await/begin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "heroes", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "concurrently \"npm run backend\" \"webpack-dev-server\"", 7 | "build": "webpack --mode production", 8 | "backend": "json-server --watch db.json --routes routes.json --port 8081 --middlewares ./node_modules/json-server-reset --delay 500" 9 | }, 10 | "dependencies": { 11 | "@fortawesome/fontawesome-svg-core": "^1.2.18", 12 | "@fortawesome/free-solid-svg-icons": "^5.8.2", 13 | "axios": "^0.19.0" 14 | }, 15 | "devDependencies": { 16 | "@typescript-eslint/eslint-plugin": "^2.3.2", 17 | "@typescript-eslint/parser": "^2.3.2", 18 | "bulma": "^0.9.3", 19 | "concurrently": "^4.1.2", 20 | "copy-webpack-plugin": "^6.4.1", 21 | "css-loader": "^3.2.0", 22 | "eslint": "^6.7.0", 23 | "eslint-config-airbnb-base": "^14.0.0", 24 | "eslint-config-prettier": "^6.7.0", 25 | "eslint-plugin-import": "^2.18.2", 26 | "eslint-plugin-prettier": "^3.1.1", 27 | "extract-text-webpack-plugin": "^4.0.0-beta.0", 28 | "json-server": "^0.15.1", 29 | "json-server-reset": "^1.2.0", 30 | "mini-css-extract-plugin": "^0.8.0", 31 | "sass": "^1.44.0", 32 | "prettier": "^1.19.1", 33 | "sass-loader": "^8.0.0", 34 | "style-loader": "^1.0.0", 35 | "ts-loader": "^6.2.0", 36 | "typescript": "^3.7.0-beta", 37 | "webpack": "^4.41.0", 38 | "webpack-cli": "^3.3.9", 39 | "webpack-dev-server": "^3.8.2" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /06-async-await/begin/routes.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/*": "/$1", 3 | "/hero": "/heroes", 4 | "/heroes/:email": "/heroes?email=:email", 5 | "/order": "/orders", 6 | "/orders/:heroId": "/orders?heroId=:heroId", 7 | "/accountreps/:heroId": "/accountreps?heroId=:heroId", 8 | "/shippingstatuses/:orderNum": "/shippingstatuses?orderNum=:orderNum", 9 | "/reset": "/reset" 10 | } 11 | -------------------------------------------------------------------------------- /06-async-await/begin/src/design/_bulma.scss: -------------------------------------------------------------------------------- 1 | @import 'bulma/bulma.sass'; 2 | 3 | // Using specific Bulma files reduces the 3 production css files from 195kb to 139kb for a 28% decrease 4 | // @import 'bulma/sass/utilities/_all.sass'; 5 | // @import 'bulma/sass/utilities/mixins.sass'; 6 | // // @import 'bulma/sass/utilities/controls.sass'; 7 | // // @import 'bulma/sass/base/generic.sass'; 8 | // // @import 'bulma/sass/base/helpers.sass'; // 277 lines 9 | // // @import 'bulma/sass/base/minireset.sass'; 10 | // // @import 'bulma/sass/layout/footer.sass'; 11 | // // @import 'bulma/sass/layout/section.sass'; // 12 lines 12 | // @import 'bulma/sass/components/card.sass'; 13 | // @import 'bulma/sass/components/list.sass'; // 39 lines 14 | // @import 'bulma/sass/components/menu.sass'; 15 | // // @import 'bulma/sass/components/modal.sass'; 16 | // @import 'bulma/sass/components/navbar.sass'; 17 | // // @import 'bulma/sass/elements/notification.sass'; 18 | // @import 'bulma/sass/elements/button.sass'; 19 | // @import 'bulma/sass/elements/container.sass'; // 25 lines 20 | // @import 'bulma/sass/elements/content.sass'; // 150 lines 21 | // @import 'bulma/sass/elements/title.sass'; // 64 lines 22 | // // // @import 'bulma/sass/elements/icon.sass'; 23 | // // @import 'bulma/sass/form/_all.sass'; 24 | // @import 'bulma/sass/grid/columns.sass'; 25 | -------------------------------------------------------------------------------- /06-async-await/begin/src/design/_variables.scss: -------------------------------------------------------------------------------- 1 | $javascript: #f9e64f; 2 | $javascript-light: #fdf190; 3 | $typescript: #4386dd; //294e80 4 | $typescript-light: #4180d3; 5 | $primary: $typescript; 6 | $primary-light: $typescript-light; 7 | $link: $primary; 8 | $info: $primary; 9 | $shade-light: #fafafa; 10 | $accent-color: #00b3e6; 11 | $accent-dark-color: #294e80; 12 | $table-background-color: #b84949; //$shade-light; 13 | -------------------------------------------------------------------------------- /06-async-await/begin/src/design/index.scss: -------------------------------------------------------------------------------- 1 | // @charset "utf-8"; 2 | // @import '~bulma/bulma'; 3 | @import 'variables'; 4 | @import 'bulma'; 5 | @import 'styles'; 6 | 7 | // Import a Google Font 8 | @import url('https://fonts.googleapis.com/css?family=Nunito:400,700'); 9 | @import url('https://use.fontawesome.com/releases/v5.4.1/css/all.css'); 10 | -------------------------------------------------------------------------------- /06-async-await/begin/src/examples/await.ts: -------------------------------------------------------------------------------- 1 | import { heroes } from './heroes'; 2 | import { Hero } from '../lib'; 3 | 4 | /** 5 | * Return a fulfilled promise after a given delay. 6 | */ 7 | const delay = (ms: number) => 8 | new Promise(resolve => setTimeout(resolve, ms)); 9 | 10 | /** 11 | * Get the heroes after a delay 12 | */ 13 | export async function getHeroesViaAsyncAwait() { 14 | // TODO - get heroes after a delay 15 | } 16 | 17 | /** 18 | * Get the heroes, 19 | * except this always causes an error 20 | * because it always is [] 21 | */ 22 | export async function getHeroesAndThrow() { 23 | // TODO - get empty array heroes after a delay 24 | } 25 | 26 | /** 27 | * Get the heroes, but wrap with a try/catch 28 | */ 29 | export async function getHeroesAndTryCatch() { 30 | // TODO - get heroes after a delay, and use try/catch 31 | } 32 | -------------------------------------------------------------------------------- /06-async-await/begin/src/examples/heroes.ts: -------------------------------------------------------------------------------- 1 | import { Hero } from "../lib"; 2 | 3 | export const heroes: Hero[] = [ 4 | { 5 | id: 10, 6 | name: 'Madelyn', 7 | description: 'the cat whisperer', 8 | email: 'madelyn@acme.com', 9 | }, 10 | { 11 | id: 20, 12 | name: 'Haley', 13 | description: 'pen wielder', 14 | email: 'haley@acme.com', 15 | }, 16 | { 17 | id: 30, 18 | name: 'Ella', 19 | description: 'fashionista', 20 | email: 'ella@acme.com', 21 | }, 22 | { 23 | id: 40, 24 | name: 'Landon', 25 | description: 'arc trooper', 26 | email: 'landon@acme.com', 27 | }, 28 | ]; 29 | -------------------------------------------------------------------------------- /06-async-await/begin/src/index.ts: -------------------------------------------------------------------------------- 1 | import './design/index.scss'; 2 | 3 | import { Hero, showFetching, showMessage } from './lib'; 4 | 5 | import { replaceHeroListComponent } from './heroes.component'; 6 | import { 7 | getHeroesViaAsyncAwait, 8 | getHeroesAndThrow, 9 | getHeroesAndTryCatch, 10 | } from './examples/await'; 11 | import { getHeroTreeAsync } from './lib/data/await'; 12 | 13 | const searchEmailElement = document.getElementById( 14 | 'search-email', 15 | ) as HTMLInputElement; 16 | const button = document.querySelector('.search-button'); 17 | searchEmailElement.addEventListener('keydown', (e: KeyboardEvent) => { 18 | if (e.code === 'Enter') render(); 19 | }); 20 | button.addEventListener('click', render); 21 | 22 | document 23 | .getElementById('async-heroes') 24 | .addEventListener('click', renderHeroesAsync); 25 | 26 | document 27 | .getElementById('async-throw') 28 | .addEventListener('click', renderHeroesButThrow); 29 | 30 | function wrapUp() { 31 | showFetching(false); 32 | } 33 | 34 | function handleErrors(error: any) { 35 | console.error('Oh no!'); 36 | console.error(error); 37 | showMessage(`Something bad happened`, 'Error'); 38 | } 39 | 40 | function showHeroes(heroes: Hero[]) { 41 | console.table(heroes); 42 | showMessage(`Returned ${heroes.length} heroes`); 43 | heroes.forEach(h => showMessage(JSON.stringify(h), 'heroes', true)); 44 | } 45 | 46 | /** 47 | * Get the heroes, renders them. 48 | * Handle errors gracefully. 49 | * Always end by turning off progress indicator. 50 | */ 51 | async function renderHeroesAsync() { 52 | showFetching(); 53 | showMessage(); 54 | // TODO - getHeroesViaAsyncAwait 55 | } 56 | 57 | /** 58 | * Get the heroes, but an error is thrown! 59 | * Handle errors gracefully? 60 | * Always end by turning off progress indicator. 61 | */ 62 | async function renderHeroesButThrow() { 63 | showFetching(); 64 | showMessage(); 65 | // TODO - getHeroesAndThrow 66 | } 67 | 68 | async function render() { 69 | showMessage(); 70 | showFetching(); 71 | /** 72 | * Get all of the hero data. 73 | * Then show the hero with the hero component. 74 | * Handle any errors 75 | * Always turn off the fetching indicator. 76 | */ 77 | // TODO 78 | } 79 | -------------------------------------------------------------------------------- /06-async-await/begin/src/lib/data/await.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosError } from 'axios'; 2 | 3 | import { apiUrl, parseList } from './config'; 4 | import { 5 | Order, 6 | Hero, 7 | AccountRepresentative, 8 | ShippingStatus, 9 | } from '../interfaces'; 10 | 11 | const getHeroAsync = async function(email: string) { 12 | // TODO 13 | }; 14 | 15 | const getOrdersAsync = async function(heroId: number) { 16 | // TODO 17 | }; 18 | 19 | const getAccountRepAsync = async function(heroId: number) { 20 | // TODO 21 | }; 22 | 23 | const getShippingStatusAsync = async function(orderNumber: number) { 24 | // TODO 25 | }; 26 | 27 | const getHeroTreeAsync = async function(email: string) { 28 | // TODO 29 | }; 30 | 31 | function handleAxiosErrors(error: AxiosError, model: string) { 32 | /** 33 | * This is a technical error, targeting the developers. 34 | * You should always log it here (lowest level). This serves the developer. 35 | * If I want to propogate this back to the callers, 36 | * I should determine how to propogate it out and if I want to transform it. 37 | */ 38 | console.error(`Developer Error: Async Data Error: ${error.message}`); 39 | 40 | /** 41 | * How do I feel about errors in this path? 42 | * Log the error here, 43 | * and let the caller know an error occurred, 44 | * but dont change the return type 45 | */ 46 | throw new Error(`Oh no! We're unable to fetch the ${model}`); 47 | 48 | /** 49 | * Throw errors or return them? 50 | * 51 | * We could pass this back every time. 52 | * the argument here is you can avoid try/catch everywhere but you instead have to package the error. 53 | * interface Message { 54 | * response: any; 55 | * error: string; 56 | * } 57 | * 58 | * return the error object to the caller, to be examined 59 | * return {response, error}; 60 | */ 61 | } 62 | 63 | export { getHeroTreeAsync }; 64 | -------------------------------------------------------------------------------- /06-async-await/begin/src/lib/data/config.ts: -------------------------------------------------------------------------------- 1 | import { AxiosResponse } from 'axios'; 2 | 3 | export const API = '/api'; 4 | 5 | let apiUrl = API; 6 | 7 | const parseList = (response: AxiosResponse) => { 8 | if (response.status !== 200) throw Error(response.statusText); 9 | if (!response.data) return []; 10 | let list: T[] = response.data; 11 | if (typeof list !== 'object') { 12 | list = []; 13 | } 14 | return list; 15 | }; 16 | 17 | export { apiUrl, parseList }; 18 | -------------------------------------------------------------------------------- /06-async-await/begin/src/lib/data/index.ts: -------------------------------------------------------------------------------- 1 | export * from './await'; 2 | export * from './config'; 3 | export * from './callback'; 4 | export * from './promise'; 5 | -------------------------------------------------------------------------------- /06-async-await/begin/src/lib/data/promise.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosResponse, AxiosError } from 'axios'; 2 | 3 | import { Order, Hero, AccountRepresentative } from '../interfaces'; 4 | import { apiUrl, parseList } from './config'; 5 | 6 | /** 7 | * Get the hero and his/her related orders and account rep 8 | * using promises 9 | */ 10 | const getHeroTreePromise = function(searchEmail: string) { 11 | let hero: Hero; 12 | 13 | return getHeroPromise(searchEmail) 14 | .then((h: Hero) => { 15 | hero = h; 16 | return h; 17 | }) 18 | .then((hero: Hero) => 19 | Promise.all([getOrdersPromise(hero.id), getAccountRepPromise(hero.id)]), 20 | ) 21 | .then((result: [Order[], AccountRepresentative]) => mergeData(result)); 22 | 23 | function mergeData(result: [Order[], AccountRepresentative]): Hero { 24 | const [orders, accountRep] = result; 25 | if (orders) { 26 | hero.orders = orders; 27 | } 28 | if (accountRep) { 29 | hero.accountRep = accountRep; 30 | } 31 | return hero; 32 | } 33 | }; 34 | 35 | /** 36 | * Get the hero 37 | */ 38 | const getHeroPromise = (email: string) => { 39 | return axios 40 | .get(`${apiUrl}/heroes?email=${email}`) 41 | .then((response: AxiosResponse) => { 42 | const data = parseList(response); 43 | const hero = data[0]; 44 | return hero; 45 | }) 46 | .catch((error: AxiosError) => handleAxiosErrors(error, 'Hero')); 47 | }; 48 | 49 | /** 50 | * Get the hero's orders 51 | */ 52 | const getOrdersPromise = function(heroId: number) { 53 | return axios 54 | .get(`${apiUrl}/orders/${heroId}`) 55 | .then((response: AxiosResponse) => parseList(response)) 56 | .catch((error: AxiosError) => handleAxiosErrors(error, 'Orders')); 57 | }; 58 | 59 | /** 60 | * Get the hero's account rep 61 | */ 62 | const getAccountRepPromise = function(heroId: number) { 63 | return axios 64 | .get(`${apiUrl}/accountreps/${heroId}`) 65 | .then((response: AxiosResponse) => { 66 | const data = parseList(response); 67 | return data[0]; 68 | }) 69 | .catch((error: AxiosError) => handleAxiosErrors(error, 'Account Reps')); 70 | }; 71 | 72 | function handleAxiosErrors(error: AxiosError, model: string) { 73 | console.error(`Developer Error: Async Data Error: ${error.message}`); 74 | return Promise.reject(`Oh no! We're unable to fetch the ${model}`); 75 | 76 | /** 77 | * Option - you could throw an error, 78 | * 79 | * but it won't be caught if you are inside 80 | * of another async callback. 81 | * So it is safer to reject. 82 | * throw new Error('User Facing Error: Something bad happened'); 83 | */ 84 | } 85 | 86 | export { getHeroTreePromise }; 87 | -------------------------------------------------------------------------------- /06-async-await/begin/src/lib/dom.ts: -------------------------------------------------------------------------------- 1 | export function setText( 2 | el: DocumentFragment | HTMLElement, 3 | selector: string, 4 | text: string, 5 | ) { 6 | el.querySelector(selector).textContent = text.toString(); 7 | return el; 8 | } 9 | 10 | export function getText(el: DocumentFragment | HTMLElement, selector: string) { 11 | return el.querySelector(selector).textContent; 12 | } 13 | 14 | export const createDiv = (...classList: string[]) => { 15 | const el = document.createElement('div'); 16 | el.classList.add(...classList); 17 | return el; 18 | }; 19 | 20 | export function cloneElementsFromTemplate(templateName: string) { 21 | const template = document.getElementById(templateName) as HTMLTemplateElement; 22 | const clone = document.importNode(template.content, true); 23 | return clone; 24 | } 25 | 26 | export function showMessage(text = '', title = 'Info', append = false) { 27 | const el = document.getElementById('message-box'); 28 | el.style.visibility = !!text ? 'visible' : 'hidden'; 29 | 30 | let newText = text; 31 | if (append) { 32 | let oldText = getText(el, '.message-body'); 33 | newText = `${oldText}\r\n${text}`; 34 | } 35 | 36 | setText(el, '.message-header', title); 37 | setText(el, '.message-body', newText); 38 | } 39 | 40 | export function showFetching(show: boolean = true) { 41 | if (show) { 42 | clearList(); 43 | } 44 | document.getElementById('progress').style.display = show ? 'block' : 'none'; 45 | } 46 | 47 | export function clearList() { 48 | const heroPlaceholder = document.querySelector('.hero-list'); 49 | const div = createDiv('hero-list'); 50 | div.innerText = ''; 51 | heroPlaceholder.replaceWith(div); 52 | } 53 | -------------------------------------------------------------------------------- /06-async-await/begin/src/lib/index.ts: -------------------------------------------------------------------------------- 1 | export * from './data'; 2 | export * from './dom'; 3 | export * from './interfaces'; 4 | export * from './modal'; 5 | export * from './timer'; 6 | -------------------------------------------------------------------------------- /06-async-await/begin/src/lib/interfaces.ts: -------------------------------------------------------------------------------- 1 | export interface Hero { 2 | id: number; 3 | name: string; 4 | description: string; 5 | email: string; 6 | orders?: Order[]; 7 | accountRep?: AccountRepresentative; 8 | } 9 | 10 | export interface Order { 11 | heroId: number; 12 | num: number; 13 | items: Item[]; 14 | shippingStatus: ShippingStatus; 15 | } 16 | 17 | export interface AccountRepresentative { 18 | repId: number; 19 | name: string; 20 | } 21 | 22 | export interface ShippingStatus { 23 | [index: number]: ShippingStatus; 24 | orderNum: number; 25 | status: string; 26 | } 27 | 28 | export interface Item { 29 | orderNum: number; 30 | name: string; 31 | qty: number; 32 | price: number; 33 | } 34 | 35 | export interface Callback { 36 | (data: T): void; 37 | } 38 | 39 | export interface CallbackError { 40 | (msg?: string): void; 41 | } 42 | -------------------------------------------------------------------------------- /06-async-await/begin/src/lib/modal.ts: -------------------------------------------------------------------------------- 1 | export const openModal = async function () { 2 | const listenerYes = () => closeModal('yes'); 3 | const listenerNo = () => closeModal('no'); 4 | document.querySelector('.modal-yes').addEventListener('click', listenerYes); 5 | document.querySelector('.modal-no').addEventListener('click', listenerNo); 6 | 7 | const modalWindow = document.getElementById('modal'); 8 | 9 | if (modalWindow.classList) { 10 | modalWindow.classList.add('is-active'); 11 | } 12 | 13 | let resolve: (value?: string) => void; 14 | const responsePromise = new Promise((res, rej) => { 15 | resolve = res; 16 | }); 17 | /** 18 | * This next line returns the promise. 19 | * The caller then waits for the promise to resolve. 20 | */ 21 | return responsePromise; 22 | 23 | function closeModal(yesno: 'yes' | 'no') { 24 | if (modalWindow.classList) { 25 | modalWindow.classList.remove('is-active'); 26 | } 27 | 28 | modalWindow 29 | .querySelector('.modal-yes') 30 | .removeEventListener('click', listenerYes); 31 | modalWindow 32 | .querySelector('.modal-no') 33 | .removeEventListener('click', listenerNo); 34 | 35 | return resolve(yesno); 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /06-async-await/begin/src/lib/timer.ts: -------------------------------------------------------------------------------- 1 | import { showMessage } from './dom'; 2 | 3 | export const sayHelloTimer = function (ms: number) { 4 | let counter = 0; 5 | showMessage(`Starting the timer`, 'Response from Timer'); 6 | 7 | const callback = (ms: number) => { 8 | counter++; 9 | showMessage( 10 | `Hello every ${ms} milliseconds. (${counter} iterations)`, 11 | 'Response from Timer', 12 | true, 13 | ); 14 | 15 | if (counter === 5) { 16 | showMessage( 17 | `Goodbye. We said hello every ${ms} milliseconds. (after ${counter} iterations)`, 18 | 'Response from Timer', 19 | true, 20 | ); 21 | clearInterval(intervalId); 22 | } 23 | }; 24 | 25 | const intervalId = setInterval(callback, ms, ms); 26 | }; 27 | -------------------------------------------------------------------------------- /06-async-await/begin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist/", 4 | "sourceMap": true, 5 | "noImplicitAny": true, 6 | // "module": "es6", 7 | "module": "commonjs", 8 | "target": "es5", 9 | // "allowJs": true, 10 | "allowSyntheticDefaultImports": true, 11 | "lib": ["es2018", "dom"] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /06-async-await/begin/webpack.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const path = require('path'); 3 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 4 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 5 | 6 | const index = 'index'; 7 | 8 | module.exports = { 9 | entry: `./src/${index}.ts`, 10 | devtool: 'inline-source-map', 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.tsx?$/, 15 | use: 'ts-loader', 16 | exclude: /node_modules/, 17 | }, 18 | { 19 | test: /\.s[ac]ss$/i, 20 | use: [ 21 | MiniCssExtractPlugin.loader, 22 | { 23 | loader: 'css-loader', 24 | }, 25 | { 26 | loader: 'sass-loader', 27 | options: { 28 | sourceMap: true, 29 | }, 30 | }, 31 | ], 32 | }, 33 | ], 34 | }, 35 | plugins: [ 36 | new MiniCssExtractPlugin({ 37 | filename: '[name].bundle.css', 38 | }), 39 | new CopyWebpackPlugin({ 40 | patterns: [ 41 | { 42 | from: `./${index}.html`, 43 | }, 44 | ], 45 | }), 46 | ], 47 | resolve: { 48 | extensions: ['.ts', '.js'], 49 | }, 50 | output: { 51 | filename: 'bundle.js', 52 | path: path.resolve(__dirname, 'dist'), 53 | }, 54 | performance: { 55 | maxEntrypointSize: 640000, 56 | maxAssetSize: 640000, 57 | }, 58 | devServer: { 59 | contentBase: 'dist', 60 | port: 8080, 61 | // Send API requests on localhost to API server get around CORS. 62 | proxy: { 63 | '/api': { 64 | target: { 65 | host: '0.0.0.0', 66 | protocol: 'http:', 67 | port: 8081, 68 | }, 69 | pathRewrite: { 70 | '^/api': '', 71 | }, 72 | }, 73 | }, 74 | }, 75 | }; 76 | -------------------------------------------------------------------------------- /06-async-await/end/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: false, 5 | }, 6 | parser: '@typescript-eslint/parser', // Specifies the ESLint parser 7 | extends: [ 8 | // 'prettier', 9 | // Uses the recommended rules from the @typescript-eslint/eslint-plugin 10 | 'plugin:@typescript-eslint/recommended', 11 | // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier 12 | 'prettier/@typescript-eslint', 13 | // Enables eslint-plugin-prettier and displays prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array. 14 | 'plugin:prettier/recommended', 15 | ], 16 | plugins: ['@typescript-eslint', 'prettier'], 17 | // watch this for explaining why some of this is here 18 | // https://www.youtube.com/watch?time_continue=239&v=YIvjKId9m2c 19 | rules: { 20 | // 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 21 | 'no-console': 'off', 22 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', 23 | '@typescript-eslint/explicit-function-return-type': 'off', 24 | '@typescript-eslint/no-use-before-define': 'off', 25 | quotes: [2, 'single', { avoidEscape: true, allowTemplateLiterals: true }], 26 | 'prettier/prettier': [ 27 | 'error', 28 | { 29 | // trailingComma: 'all', 30 | // singleQuote: true, 31 | // printWidth: 80, 32 | }, 33 | ], 34 | }, 35 | }; 36 | -------------------------------------------------------------------------------- /06-async-await/end/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | /node_modules 10 | node_modules 11 | 12 | # IDEs and editors 13 | /.idea 14 | .project 15 | .classpath 16 | .c9/ 17 | *.launch 18 | .settings/ 19 | *.sublime-workspace 20 | 21 | # IDE - VSCode 22 | .vscode/* 23 | !.vscode/settings.json 24 | !.vscode/tasks.json 25 | !.vscode/launch.json 26 | !.vscode/extensions.json 27 | 28 | # misc 29 | /.sass-cache 30 | /connect.lock 31 | /coverage 32 | /libpeerconnection.log 33 | npm-debug.log 34 | yarn-error.log 35 | testem.log 36 | /typings 37 | 38 | # System Files 39 | .DS_Store 40 | Thumbs.db 41 | -------------------------------------------------------------------------------- /06-async-await/end/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 80, 3 | tabWidth: 2, 4 | bracketSpacing: true, 5 | singleQuote: true, 6 | trailingComma: 'all', 7 | }; 8 | -------------------------------------------------------------------------------- /06-async-await/end/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.enable": true, 3 | "workbench.colorCustomizations": { 4 | "activityBar.background": "#fbed80", 5 | "activityBar.activeBorder": "#06b9a5", 6 | "activityBar.foreground": "#15202b", 7 | "activityBar.inactiveForeground": "#15202b99", 8 | "activityBarBadge.background": "#06b9a5", 9 | "activityBarBadge.foreground": "#15202b", 10 | "titleBar.activeBackground": "#f9e64f", 11 | "titleBar.inactiveBackground": "#f9e64f99", 12 | "titleBar.activeForeground": "#15202b", 13 | "titleBar.inactiveForeground": "#15202b99", 14 | "statusBar.background": "#f9e64f", 15 | "statusBarItem.hoverBackground": "#f7df1e", 16 | "statusBar.foreground": "#15202b", 17 | "panel.border": "#fbed80", 18 | "sideBar.border": "#fbed80", 19 | "editorGroup.border": "#fbed80", 20 | "tab.activeBorder": "#fbed80" 21 | }, 22 | "peacock.color": "#f9e64f", 23 | "typescript.tsdk": "node_modules/typescript/lib" 24 | } -------------------------------------------------------------------------------- /06-async-await/end/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnpapa/typescript-async/bd7bfd359844248efa2b834b438f53dc36aec393/06-async-await/end/README.md -------------------------------------------------------------------------------- /06-async-await/end/compare.md: -------------------------------------------------------------------------------- 1 | # compare files 2 | 3 | ```typescript 4 | // Callback 5 | firstFunction(args1, function() { 6 | secondFunction(args2, function() { 7 | thirdFunction(args3, function() { 8 | // And so on... 9 | }); 10 | }); 11 | }); 12 | 13 | // Promise 14 | function getThings(a) { 15 | return getThing1(a).then(b => getThing2(b)); 16 | then(c => getThing3(c)); 17 | } 18 | 19 | function basicPromise() { 20 | return firstFunction(args1) 21 | .then(args2 => secondFunction(args2)) 22 | .then(args3 => thirdFunction(args3)); 23 | } 24 | 25 | // Async/Await 26 | async function getThings(a) { 27 | const b = await getThing1(a); 28 | const c = await getThing2(b); 29 | return await getThing3(c); 30 | } 31 | 32 | function basicAsyncAwait() { 33 | const args2 = await firstFunction(args1); 34 | const args3 = await secondFunction(args2); 35 | return await thirdFunction(args3); 36 | } 37 | 38 | const getHeroTreeCallback = function(email: string, callback: any) { 39 | getHeroCallback(email, hero => { 40 | getOrdersCallback(hero.id, orders => { 41 | hero.orders = orders; 42 | getAccountRepCallback(hero.id, accountRep => { 43 | hero.accountRep = accountRep; 44 | callback(hero); 45 | }); 46 | }); 47 | }); 48 | }; 49 | 50 | const getHeroTreePromise = function(searchEmail: string) { 51 | let hero: Hero; 52 | return getHeroPromise(searchEmail) 53 | .then((hero: Hero) => Promise.all([getOrders(hero), getAccountRep(hero)])) 54 | .then((result: [Order[], AccountRepresentative]) => { 55 | const [orders, accountRep] = result; 56 | hero.orders = orders; 57 | hero.accountRep = accountRep; 58 | return hero; 59 | }); 60 | }; 61 | 62 | const getHeroTreeAsync = async function(email: string) { 63 | const hero = await getHeroAsync(email); 64 | const [orders, accountRep] = await Promise.all([ 65 | getOrdersAsync(hero), 66 | getAccountRepAsync(hero), 67 | ]); 68 | hero.orders = orders; 69 | hero.accountRep = accountRep; 70 | return hero; 71 | }; 72 | ``` 73 | -------------------------------------------------------------------------------- /06-async-await/end/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "heroes", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "concurrently \"npm run backend\" \"webpack-dev-server\"", 7 | "build": "webpack --mode production", 8 | "backend": "json-server --watch db.json --routes routes.json --port 8081 --middlewares ./node_modules/json-server-reset --delay 500" 9 | }, 10 | "dependencies": { 11 | "@fortawesome/fontawesome-svg-core": "^1.2.18", 12 | "@fortawesome/free-solid-svg-icons": "^5.8.2", 13 | "axios": "^0.19.0" 14 | }, 15 | "devDependencies": { 16 | "@typescript-eslint/eslint-plugin": "^2.3.2", 17 | "@typescript-eslint/parser": "^2.3.2", 18 | "bulma": "^0.9.3", 19 | "concurrently": "^4.1.2", 20 | "copy-webpack-plugin": "^6.4.1", 21 | "css-loader": "^3.2.0", 22 | "eslint": "^6.7.0", 23 | "eslint-config-airbnb-base": "^14.0.0", 24 | "eslint-config-prettier": "^6.7.0", 25 | "eslint-plugin-import": "^2.18.2", 26 | "eslint-plugin-prettier": "^3.1.1", 27 | "extract-text-webpack-plugin": "^4.0.0-beta.0", 28 | "json-server": "^0.15.1", 29 | "json-server-reset": "^1.2.0", 30 | "mini-css-extract-plugin": "^0.8.0", 31 | "sass": "^1.44.0", 32 | "prettier": "^1.19.1", 33 | "sass-loader": "^8.0.0", 34 | "style-loader": "^1.0.0", 35 | "ts-loader": "^6.2.0", 36 | "typescript": "^3.7.0-beta", 37 | "webpack": "^4.41.0", 38 | "webpack-cli": "^3.3.9", 39 | "webpack-dev-server": "^3.8.2" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /06-async-await/end/routes.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/*": "/$1", 3 | "/hero": "/heroes", 4 | "/heroes/:email": "/heroes?email=:email", 5 | "/order": "/orders", 6 | "/orders/:heroId": "/orders?heroId=:heroId", 7 | "/accountreps/:heroId": "/accountreps?heroId=:heroId", 8 | "/shippingstatuses/:orderNum": "/shippingstatuses?orderNum=:orderNum", 9 | "/reset": "/reset" 10 | } 11 | -------------------------------------------------------------------------------- /06-async-await/end/src/design/_bulma.scss: -------------------------------------------------------------------------------- 1 | @import 'bulma/bulma.sass'; 2 | 3 | // Using specific Bulma files reduces the 3 production css files from 195kb to 139kb for a 28% decrease 4 | // @import 'bulma/sass/utilities/_all.sass'; 5 | // @import 'bulma/sass/utilities/mixins.sass'; 6 | // // @import 'bulma/sass/utilities/controls.sass'; 7 | // // @import 'bulma/sass/base/generic.sass'; 8 | // // @import 'bulma/sass/base/helpers.sass'; // 277 lines 9 | // // @import 'bulma/sass/base/minireset.sass'; 10 | // // @import 'bulma/sass/layout/footer.sass'; 11 | // // @import 'bulma/sass/layout/section.sass'; // 12 lines 12 | // @import 'bulma/sass/components/card.sass'; 13 | // @import 'bulma/sass/components/list.sass'; // 39 lines 14 | // @import 'bulma/sass/components/menu.sass'; 15 | // // @import 'bulma/sass/components/modal.sass'; 16 | // @import 'bulma/sass/components/navbar.sass'; 17 | // // @import 'bulma/sass/elements/notification.sass'; 18 | // @import 'bulma/sass/elements/button.sass'; 19 | // @import 'bulma/sass/elements/container.sass'; // 25 lines 20 | // @import 'bulma/sass/elements/content.sass'; // 150 lines 21 | // @import 'bulma/sass/elements/title.sass'; // 64 lines 22 | // // // @import 'bulma/sass/elements/icon.sass'; 23 | // // @import 'bulma/sass/form/_all.sass'; 24 | // @import 'bulma/sass/grid/columns.sass'; 25 | -------------------------------------------------------------------------------- /06-async-await/end/src/design/_variables.scss: -------------------------------------------------------------------------------- 1 | $javascript: #f9e64f; 2 | $javascript-light: #fdf190; 3 | $typescript: #4386dd; //294e80 4 | $typescript-light: #4180d3; 5 | $primary: $typescript; 6 | $primary-light: $typescript-light; 7 | $link: $primary; 8 | $info: $primary; 9 | $shade-light: #fafafa; 10 | $accent-color: #00b3e6; 11 | $accent-dark-color: #294e80; 12 | $table-background-color: #b84949; //$shade-light; 13 | -------------------------------------------------------------------------------- /06-async-await/end/src/design/index.scss: -------------------------------------------------------------------------------- 1 | // @charset "utf-8"; 2 | // @import '~bulma/bulma'; 3 | @import 'variables'; 4 | @import 'bulma'; 5 | @import 'styles'; 6 | 7 | // Import a Google Font 8 | @import url('https://fonts.googleapis.com/css?family=Nunito:400,700'); 9 | @import url('https://use.fontawesome.com/releases/v5.4.1/css/all.css'); 10 | -------------------------------------------------------------------------------- /06-async-await/end/src/examples/await.ts: -------------------------------------------------------------------------------- 1 | import { heroes } from './heroes'; 2 | import { Hero } from '../lib'; 3 | 4 | /** 5 | * Return a fulfilled promise after a given delay. 6 | */ 7 | const delay = (ms: number) => 8 | new Promise(resolve => setTimeout(resolve, ms)); 9 | 10 | /** 11 | * Get the heroes after a delay 12 | */ 13 | export async function getHeroesViaAsyncAwait() { 14 | await delay(1000); 15 | return heroes; 16 | } 17 | 18 | /** 19 | * Get the heroes, 20 | * except this always causes an error 21 | * because it always is [] 22 | */ 23 | export async function getHeroesAndThrow() { 24 | await delay(1000); 25 | const heroes = [] as Hero[]; 26 | if (!heroes || !heroes.length) { 27 | throw Error('Uh oh! Errors!'); 28 | } 29 | return heroes; 30 | } 31 | 32 | /** 33 | * Get the heroes, but wrap with a try/catch 34 | */ 35 | export async function getHeroesAndTryCatch() { 36 | try { 37 | await delay(1000); 38 | return heroes; 39 | } catch (error) { 40 | console.error(error); 41 | throw Error('Uh oh! Errors!'); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /06-async-await/end/src/examples/heroes.ts: -------------------------------------------------------------------------------- 1 | import { Hero } from "../lib"; 2 | 3 | export const heroes: Hero[] = [ 4 | { 5 | id: 10, 6 | name: 'Madelyn', 7 | description: 'the cat whisperer', 8 | email: 'madelyn@acme.com', 9 | }, 10 | { 11 | id: 20, 12 | name: 'Haley', 13 | description: 'pen wielder', 14 | email: 'haley@acme.com', 15 | }, 16 | { 17 | id: 30, 18 | name: 'Ella', 19 | description: 'fashionista', 20 | email: 'ella@acme.com', 21 | }, 22 | { 23 | id: 40, 24 | name: 'Landon', 25 | description: 'arc trooper', 26 | email: 'landon@acme.com', 27 | }, 28 | ]; 29 | -------------------------------------------------------------------------------- /06-async-await/end/src/lib/data/config.ts: -------------------------------------------------------------------------------- 1 | import { AxiosResponse } from 'axios'; 2 | 3 | export const API = '/api'; 4 | 5 | let apiUrl = API; 6 | 7 | const parseList = (response: AxiosResponse) => { 8 | if (response.status !== 200) throw Error(response.statusText); 9 | if (!response.data) return []; 10 | let list: T[] = response.data; 11 | if (typeof list !== 'object') { 12 | list = []; 13 | } 14 | return list; 15 | }; 16 | 17 | export { apiUrl, parseList }; 18 | -------------------------------------------------------------------------------- /06-async-await/end/src/lib/data/index.ts: -------------------------------------------------------------------------------- 1 | export * from './await'; 2 | export * from './config'; 3 | export * from './callback'; 4 | export * from './promise'; 5 | -------------------------------------------------------------------------------- /06-async-await/end/src/lib/dom.ts: -------------------------------------------------------------------------------- 1 | export function setText( 2 | el: DocumentFragment | HTMLElement, 3 | selector: string, 4 | text: string, 5 | ) { 6 | el.querySelector(selector).textContent = text.toString(); 7 | return el; 8 | } 9 | 10 | export function getText(el: DocumentFragment | HTMLElement, selector: string) { 11 | return el.querySelector(selector).textContent; 12 | } 13 | 14 | export const createDiv = (...classList: string[]) => { 15 | const el = document.createElement('div'); 16 | el.classList.add(...classList); 17 | return el; 18 | }; 19 | 20 | export function cloneElementsFromTemplate(templateName: string) { 21 | const template = document.getElementById(templateName) as HTMLTemplateElement; 22 | const clone = document.importNode(template.content, true); 23 | return clone; 24 | } 25 | 26 | export function showMessage(text = '', title = 'Info', append = false) { 27 | const el = document.getElementById('message-box'); 28 | el.style.visibility = !!text ? 'visible' : 'hidden'; 29 | 30 | let newText = text; 31 | if (append) { 32 | let oldText = getText(el, '.message-body'); 33 | newText = `${oldText}\r\n${text}`; 34 | } 35 | 36 | setText(el, '.message-header', title); 37 | setText(el, '.message-body', newText); 38 | } 39 | 40 | export function showFetching(show: boolean = true) { 41 | if (show) { 42 | clearList(); 43 | } 44 | document.getElementById('progress').style.display = show ? 'block' : 'none'; 45 | } 46 | 47 | export function clearList() { 48 | const heroPlaceholder = document.querySelector('.hero-list'); 49 | const div = createDiv('hero-list'); 50 | div.innerText = ''; 51 | heroPlaceholder.replaceWith(div); 52 | } 53 | -------------------------------------------------------------------------------- /06-async-await/end/src/lib/index.ts: -------------------------------------------------------------------------------- 1 | export * from './data'; 2 | export * from './dom'; 3 | export * from './interfaces'; 4 | export * from './modal'; 5 | export * from './timer'; 6 | -------------------------------------------------------------------------------- /06-async-await/end/src/lib/interfaces.ts: -------------------------------------------------------------------------------- 1 | export interface Hero { 2 | id: number; 3 | name: string; 4 | description: string; 5 | email: string; 6 | orders?: Order[]; 7 | accountRep?: AccountRepresentative; 8 | } 9 | 10 | export interface Order { 11 | heroId: number; 12 | num: number; 13 | items: Item[]; 14 | shippingStatus: ShippingStatus; 15 | } 16 | 17 | export interface AccountRepresentative { 18 | repId: number; 19 | name: string; 20 | } 21 | 22 | export interface ShippingStatus { 23 | [index: number]: ShippingStatus; 24 | orderNum: number; 25 | status: string; 26 | } 27 | 28 | export interface Item { 29 | orderNum: number; 30 | name: string; 31 | qty: number; 32 | price: number; 33 | } 34 | 35 | export interface Callback { 36 | (data: T): void; 37 | } 38 | 39 | export interface CallbackError { 40 | (msg?: string): void; 41 | } 42 | -------------------------------------------------------------------------------- /06-async-await/end/src/lib/modal.ts: -------------------------------------------------------------------------------- 1 | export const openModal = async function () { 2 | const listenerYes = () => closeModal('yes'); 3 | const listenerNo = () => closeModal('no'); 4 | document.querySelector('.modal-yes').addEventListener('click', listenerYes); 5 | document.querySelector('.modal-no').addEventListener('click', listenerNo); 6 | 7 | const modalWindow = document.getElementById('modal'); 8 | 9 | if (modalWindow.classList) { 10 | modalWindow.classList.add('is-active'); 11 | } 12 | 13 | let resolve: (value?: string) => void; 14 | const responsePromise = new Promise((res, rej) => { 15 | resolve = res; 16 | }); 17 | /** 18 | * This next line returns the promise. 19 | * The caller then waits for the promise to resolve. 20 | */ 21 | return responsePromise; 22 | 23 | function closeModal(yesno: 'yes' | 'no') { 24 | if (modalWindow.classList) { 25 | modalWindow.classList.remove('is-active'); 26 | } 27 | 28 | modalWindow 29 | .querySelector('.modal-yes') 30 | .removeEventListener('click', listenerYes); 31 | modalWindow 32 | .querySelector('.modal-no') 33 | .removeEventListener('click', listenerNo); 34 | 35 | return resolve(yesno); 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /06-async-await/end/src/lib/timer.ts: -------------------------------------------------------------------------------- 1 | import { showMessage } from './dom'; 2 | 3 | export const sayHelloTimer = function (ms: number) { 4 | let counter = 0; 5 | showMessage(`Starting the timer`, 'Response from Timer'); 6 | 7 | const callback = (ms: number) => { 8 | counter++; 9 | showMessage( 10 | `Hello every ${ms} milliseconds. (${counter} iterations)`, 11 | 'Response from Timer', 12 | true, 13 | ); 14 | 15 | if (counter === 5) { 16 | showMessage( 17 | `Goodbye. We said hello every ${ms} milliseconds. (after ${counter} iterations)`, 18 | 'Response from Timer', 19 | true, 20 | ); 21 | clearInterval(intervalId); 22 | } 23 | }; 24 | 25 | const intervalId = setInterval(callback, ms, ms); 26 | }; 27 | -------------------------------------------------------------------------------- /06-async-await/end/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist/", 4 | "sourceMap": true, 5 | "noImplicitAny": true, 6 | // "module": "es6", 7 | "module": "commonjs", 8 | "target": "es5", 9 | // "allowJs": true, 10 | "allowSyntheticDefaultImports": true, 11 | "lib": ["es2018", "dom"] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /06-async-await/end/webpack.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const path = require('path'); 3 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 4 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 5 | 6 | const index = 'index'; 7 | 8 | module.exports = { 9 | entry: `./src/${index}.ts`, 10 | devtool: 'inline-source-map', 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.tsx?$/, 15 | use: 'ts-loader', 16 | exclude: /node_modules/, 17 | }, 18 | { 19 | test: /\.s[ac]ss$/i, 20 | use: [ 21 | MiniCssExtractPlugin.loader, 22 | { 23 | loader: 'css-loader', 24 | }, 25 | { 26 | loader: 'sass-loader', 27 | options: { 28 | sourceMap: true, 29 | }, 30 | }, 31 | ], 32 | }, 33 | ], 34 | }, 35 | plugins: [ 36 | new MiniCssExtractPlugin({ 37 | filename: '[name].bundle.css', 38 | }), 39 | new CopyWebpackPlugin({ 40 | patterns: [ 41 | { 42 | from: `./${index}.html`, 43 | }, 44 | ], 45 | }), 46 | ], 47 | resolve: { 48 | extensions: ['.ts', '.js'], 49 | }, 50 | output: { 51 | filename: 'bundle.js', 52 | path: path.resolve(__dirname, 'dist'), 53 | }, 54 | performance: { 55 | maxEntrypointSize: 640000, 56 | maxAssetSize: 640000, 57 | }, 58 | devServer: { 59 | contentBase: 'dist', 60 | port: 8080, 61 | // Send API requests on localhost to API server get around CORS. 62 | proxy: { 63 | '/api': { 64 | target: { 65 | host: '0.0.0.0', 66 | protocol: 'http:', 67 | port: 8081, 68 | }, 69 | pathRewrite: { 70 | '^/api': '', 71 | }, 72 | }, 73 | }, 74 | }, 75 | }; 76 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Creating Asynchronous Code with TypeScript 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typescript-async", 3 | "version": "1.0.0", 4 | "description": "Courseware for Creating Asynchronous TypeScript Code on Pluralsight", 5 | "main": "index.js", 6 | "scripts": {}, 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/johnpapa/typescript-async.git" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "ISC", 14 | "bugs": { 15 | "url": "https://github.com/johnpapa/typescript-async/issues" 16 | }, 17 | "homepage": "https://github.com/johnpapa/typescript-async#readme", 18 | "dependencies": {}, 19 | "devDependencies": { 20 | "eslint": "^6.7.0", 21 | "prettier": "^1.19.1" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /xx-final/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: false, 5 | }, 6 | parser: '@typescript-eslint/parser', // Specifies the ESLint parser 7 | extends: [ 8 | // 'prettier', 9 | // Uses the recommended rules from the @typescript-eslint/eslint-plugin 10 | 'plugin:@typescript-eslint/recommended', 11 | // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier 12 | 'prettier/@typescript-eslint', 13 | // Enables eslint-plugin-prettier and displays prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array. 14 | 'plugin:prettier/recommended', 15 | ], 16 | plugins: ['@typescript-eslint', 'prettier'], 17 | // watch this for explaining why some of this is here 18 | // https://www.youtube.com/watch?time_continue=239&v=YIvjKId9m2c 19 | rules: { 20 | // 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 21 | 'no-console': 'off', 22 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', 23 | '@typescript-eslint/explicit-function-return-type': 'off', 24 | '@typescript-eslint/no-use-before-define': 'off', 25 | quotes: [2, 'single', { avoidEscape: true, allowTemplateLiterals: true }], 26 | 'prettier/prettier': [ 27 | 'error', 28 | { 29 | // trailingComma: 'all', 30 | // singleQuote: true, 31 | // printWidth: 80, 32 | }, 33 | ], 34 | }, 35 | }; 36 | -------------------------------------------------------------------------------- /xx-final/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | /node_modules 10 | node_modules 11 | 12 | # IDEs and editors 13 | /.idea 14 | .project 15 | .classpath 16 | .c9/ 17 | *.launch 18 | .settings/ 19 | *.sublime-workspace 20 | 21 | # IDE - VSCode 22 | .vscode/* 23 | !.vscode/settings.json 24 | !.vscode/tasks.json 25 | !.vscode/launch.json 26 | !.vscode/extensions.json 27 | 28 | # misc 29 | /.sass-cache 30 | /connect.lock 31 | /coverage 32 | /libpeerconnection.log 33 | npm-debug.log 34 | yarn-error.log 35 | testem.log 36 | /typings 37 | 38 | # System Files 39 | .DS_Store 40 | Thumbs.db 41 | -------------------------------------------------------------------------------- /xx-final/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 80, 3 | tabWidth: 2, 4 | bracketSpacing: true, 5 | singleQuote: true, 6 | trailingComma: 'all', 7 | }; 8 | -------------------------------------------------------------------------------- /xx-final/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.enable": true, 3 | "workbench.colorCustomizations": { 4 | "activityBar.background": "#fbed80", 5 | "activityBar.activeBorder": "#06b9a5", 6 | "activityBar.foreground": "#15202b", 7 | "activityBar.inactiveForeground": "#15202b99", 8 | "activityBarBadge.background": "#06b9a5", 9 | "activityBarBadge.foreground": "#15202b", 10 | "titleBar.activeBackground": "#f9e64f", 11 | "titleBar.inactiveBackground": "#f9e64f99", 12 | "titleBar.activeForeground": "#15202b", 13 | "titleBar.inactiveForeground": "#15202b99", 14 | "statusBar.background": "#f9e64f", 15 | "statusBarItem.hoverBackground": "#f7df1e", 16 | "statusBar.foreground": "#15202b", 17 | "panel.border": "#fbed80", 18 | "sideBar.border": "#fbed80", 19 | "editorGroup.border": "#fbed80", 20 | "tab.activeBorder": "#fbed80" 21 | }, 22 | "peacock.color": "#f9e64f", 23 | "typescript.tsdk": "node_modules/typescript/lib" 24 | } -------------------------------------------------------------------------------- /xx-final/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnpapa/typescript-async/bd7bfd359844248efa2b834b438f53dc36aec393/xx-final/README.md -------------------------------------------------------------------------------- /xx-final/compare.md: -------------------------------------------------------------------------------- 1 | # compare files 2 | 3 | ```typescript 4 | // Callback 5 | firstFunction(args1, function() { 6 | secondFunction(args2, function() { 7 | thirdFunction(args3, function() { 8 | // And so on... 9 | }); 10 | }); 11 | }); 12 | 13 | // Promise 14 | function getThings(a) { 15 | return getThing1(a).then(b => getThing2(b)); 16 | then(c => getThing3(c)); 17 | } 18 | 19 | function basicPromise() { 20 | return firstFunction(args1) 21 | .then(args2 => secondFunction(args2)) 22 | .then(args3 => thirdFunction(args3)); 23 | } 24 | 25 | // Async/Await 26 | async function getThings(a) { 27 | const b = await getThing1(a); 28 | const c = await getThing2(b); 29 | return await getThing3(c); 30 | } 31 | 32 | function basicAsyncAwait() { 33 | const args2 = await firstFunction(args1); 34 | const args3 = await secondFunction(args2); 35 | return await thirdFunction(args3); 36 | } 37 | 38 | const getHeroTreeCallback = function(email: string, callback: any) { 39 | getHeroCallback(email, hero => { 40 | getOrdersCallback(hero.id, orders => { 41 | hero.orders = orders; 42 | getAccountRepCallback(hero.id, accountRep => { 43 | hero.accountRep = accountRep; 44 | callback(hero); 45 | }); 46 | }); 47 | }); 48 | }; 49 | 50 | const getHeroTreePromise = function(searchEmail: string) { 51 | let hero: Hero; 52 | return getHeroPromise(searchEmail) 53 | .then((hero: Hero) => Promise.all([getOrders(hero), getAccountRep(hero)])) 54 | .then((result: [Order[], AccountRepresentative]) => { 55 | const [orders, accountRep] = result; 56 | hero.orders = orders; 57 | hero.accountRep = accountRep; 58 | return hero; 59 | }); 60 | }; 61 | 62 | const getHeroTreeAsync = async function(email: string) { 63 | const hero = await getHeroAsync(email); 64 | const [orders, accountRep] = await Promise.all([ 65 | getOrdersAsync(hero), 66 | getAccountRepAsync(hero), 67 | ]); 68 | hero.orders = orders; 69 | hero.accountRep = accountRep; 70 | return hero; 71 | }; 72 | ``` 73 | -------------------------------------------------------------------------------- /xx-final/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "heroes", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "concurrently \"npm run backend\" \"webpack-dev-server\"", 7 | "build": "webpack --mode production", 8 | "backend": "json-server --watch db.json --routes routes.json --port 8081 --middlewares ./node_modules/json-server-reset --delay 500" 9 | }, 10 | "dependencies": { 11 | "@fortawesome/fontawesome-svg-core": "^1.2.18", 12 | "@fortawesome/free-solid-svg-icons": "^5.8.2", 13 | "axios": "^0.24.0" 14 | }, 15 | "devDependencies": { 16 | "@typescript-eslint/eslint-plugin": "^2.3.2", 17 | "@typescript-eslint/parser": "^2.3.2", 18 | "bulma": "^0.9.3", 19 | "concurrently": "^4.1.2", 20 | "copy-webpack-plugin": "^6.4.1", 21 | "css-loader": "^3.2.0", 22 | "eslint": "^6.7.0", 23 | "eslint-config-airbnb-base": "^14.0.0", 24 | "eslint-config-prettier": "^6.7.0", 25 | "eslint-plugin-import": "^2.18.2", 26 | "eslint-plugin-prettier": "^3.1.1", 27 | "extract-text-webpack-plugin": "^4.0.0-beta.0", 28 | "json-server": "^0.17.0", 29 | "json-server-reset": "^1.2.0", 30 | "mini-css-extract-plugin": "^0.8.0", 31 | "prettier": "^1.19.1", 32 | "sass": "^1.44.0", 33 | "sass-loader": "^8.0.0", 34 | "style-loader": "^1.0.0", 35 | "ts-loader": "^6.2.0", 36 | "typescript": "^3.7.0-beta", 37 | "webpack": "^4.41.0", 38 | "webpack-cli": "^3.3.9", 39 | "webpack-dev-server": "^3.8.2" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /xx-final/routes.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/*": "/$1", 3 | "/hero": "/heroes", 4 | "/heroes/:email": "/heroes?email=:email", 5 | "/order": "/orders", 6 | "/orders/:heroId": "/orders?heroId=:heroId", 7 | "/accountreps/:heroId": "/accountreps?heroId=:heroId", 8 | "/shippingstatuses/:orderNum": "/shippingstatuses?orderNum=:orderNum", 9 | "/reset": "/reset" 10 | } 11 | -------------------------------------------------------------------------------- /xx-final/src/design/_bulma.scss: -------------------------------------------------------------------------------- 1 | @import 'bulma/bulma.sass'; 2 | 3 | // Using specific Bulma files reduces the 3 production css files from 195kb to 139kb for a 28% decrease 4 | // @import 'bulma/sass/utilities/_all.sass'; 5 | // @import 'bulma/sass/utilities/mixins.sass'; 6 | // // @import 'bulma/sass/utilities/controls.sass'; 7 | // // @import 'bulma/sass/base/generic.sass'; 8 | // // @import 'bulma/sass/base/helpers.sass'; // 277 lines 9 | // // @import 'bulma/sass/base/minireset.sass'; 10 | // // @import 'bulma/sass/layout/footer.sass'; 11 | // // @import 'bulma/sass/layout/section.sass'; // 12 lines 12 | // @import 'bulma/sass/components/card.sass'; 13 | // @import 'bulma/sass/components/list.sass'; // 39 lines 14 | // @import 'bulma/sass/components/menu.sass'; 15 | // // @import 'bulma/sass/components/modal.sass'; 16 | // @import 'bulma/sass/components/navbar.sass'; 17 | // // @import 'bulma/sass/elements/notification.sass'; 18 | // @import 'bulma/sass/elements/button.sass'; 19 | // @import 'bulma/sass/elements/container.sass'; // 25 lines 20 | // @import 'bulma/sass/elements/content.sass'; // 150 lines 21 | // @import 'bulma/sass/elements/title.sass'; // 64 lines 22 | // // // @import 'bulma/sass/elements/icon.sass'; 23 | // // @import 'bulma/sass/form/_all.sass'; 24 | // @import 'bulma/sass/grid/columns.sass'; 25 | -------------------------------------------------------------------------------- /xx-final/src/design/_variables.scss: -------------------------------------------------------------------------------- 1 | $javascript: #f9e64f; 2 | $javascript-light: #fdf190; 3 | $typescript: #4386dd; //294e80 4 | $typescript-light: #4180d3; 5 | $primary: $typescript; 6 | $primary-light: $typescript-light; 7 | $link: $primary; 8 | $info: $primary; 9 | $shade-light: #fafafa; 10 | $accent-color: #00b3e6; 11 | $accent-dark-color: #294e80; 12 | $table-background-color: #b84949; //$shade-light; 13 | -------------------------------------------------------------------------------- /xx-final/src/design/index.scss: -------------------------------------------------------------------------------- 1 | // @charset "utf-8"; 2 | // @import '~bulma/bulma'; 3 | @import 'variables'; 4 | @import 'bulma'; 5 | @import 'styles'; 6 | 7 | // Import a Google Font 8 | @import url('https://fonts.googleapis.com/css?family=Nunito:400,700'); 9 | @import url('https://use.fontawesome.com/releases/v5.4.1/css/all.css'); 10 | -------------------------------------------------------------------------------- /xx-final/src/lib/data/config.ts: -------------------------------------------------------------------------------- 1 | import { AxiosResponse } from 'axios'; 2 | 3 | export const API = '/api'; 4 | 5 | let apiUrl = API; 6 | 7 | const dev = { 8 | breakAPI() { 9 | apiUrl = '/badapiurl'; 10 | }, 11 | fixAPI() { 12 | apiUrl = API; 13 | }, 14 | }; 15 | 16 | const parseList = (response: AxiosResponse) => { 17 | if (response.status !== 200) throw Error(response.statusText); 18 | if (!response.data) return []; 19 | let list: T[] = response.data; 20 | if (typeof list !== 'object') { 21 | list = []; 22 | } 23 | return list; 24 | }; 25 | 26 | export { apiUrl, dev, parseList }; 27 | -------------------------------------------------------------------------------- /xx-final/src/lib/data/index.ts: -------------------------------------------------------------------------------- 1 | export * from './config'; 2 | export * from './callback'; 3 | export * from './promise'; 4 | export * from './await'; 5 | -------------------------------------------------------------------------------- /xx-final/src/lib/dom.ts: -------------------------------------------------------------------------------- 1 | export function setText( 2 | el: DocumentFragment | HTMLElement, 3 | selector: string, 4 | text: string, 5 | ) { 6 | el.querySelector(selector).textContent = text.toString(); 7 | return el; 8 | } 9 | 10 | export function getText(el: DocumentFragment | HTMLElement, selector: string) { 11 | return el.querySelector(selector).textContent; 12 | } 13 | 14 | export const createDiv = (...classList: string[]) => { 15 | const el = document.createElement('div'); 16 | el.classList.add(...classList); 17 | return el; 18 | }; 19 | 20 | export function cloneElementsFromTemplate(templateName: string) { 21 | const template = document.getElementById(templateName) as HTMLTemplateElement; 22 | const clone = document.importNode(template.content, true); 23 | return clone; 24 | } 25 | 26 | export function showMessage(text = '', title = 'Info', append = false) { 27 | const el = document.getElementById('message-box'); 28 | el.style.visibility = !!text ? 'visible' : 'hidden'; 29 | 30 | let newText = text; 31 | if (append) { 32 | let oldText = getText(el, '.message-body'); 33 | newText = `${oldText}\r\n${text}`; 34 | } 35 | 36 | setText(el, '.message-header', title); 37 | setText(el, '.message-body', newText); 38 | } 39 | 40 | export function showFetching(selector: string) { 41 | const progressClone = cloneElementsFromTemplate('progress-template'); 42 | const heroPlaceholder = document.querySelector(selector); 43 | heroPlaceholder.replaceWith(progressClone); 44 | } 45 | -------------------------------------------------------------------------------- /xx-final/src/lib/index.ts: -------------------------------------------------------------------------------- 1 | export * from './data'; 2 | export * from './dom'; 3 | export * from './interfaces'; 4 | export * from './modal'; 5 | export * from './timer'; 6 | -------------------------------------------------------------------------------- /xx-final/src/lib/interfaces.ts: -------------------------------------------------------------------------------- 1 | export interface Hero { 2 | id: number; 3 | name: string; 4 | description: string; 5 | email: string; 6 | orders?: Order[]; 7 | accountRep?: AccountRepresentative; 8 | } 9 | 10 | export interface Order { 11 | heroId: number; 12 | num: number; 13 | items: Item[]; 14 | shippingStatus: ShippingStatus; 15 | } 16 | 17 | export interface AccountRepresentative { 18 | repId: number; 19 | name: string; 20 | } 21 | 22 | export interface ShippingStatus { 23 | [index: number]: ShippingStatus; 24 | orderNum: number; 25 | status: string; 26 | } 27 | 28 | export interface Item { 29 | orderNum: number; 30 | name: string; 31 | qty: number; 32 | price: number; 33 | } 34 | 35 | export interface Callback { 36 | (data: T): void; 37 | } 38 | 39 | export interface CallbackError { 40 | (msg?: string): void; 41 | } 42 | -------------------------------------------------------------------------------- /xx-final/src/lib/modal.ts: -------------------------------------------------------------------------------- 1 | export const openModal = async function () { 2 | const listenerYes = () => closeModal('yes'); 3 | const listenerNo = () => closeModal('no'); 4 | document.querySelector('.modal-yes').addEventListener('click', listenerYes); 5 | document.querySelector('.modal-no').addEventListener('click', listenerNo); 6 | 7 | const modalWindow = document.getElementById('modal'); 8 | 9 | if (modalWindow.classList) { 10 | modalWindow.classList.add('is-active'); 11 | } 12 | 13 | let resolve: (value?: string) => void; 14 | const responsePromise = new Promise((res, rej) => { 15 | resolve = res; 16 | }); 17 | /** 18 | * This next line returns the promise. 19 | * The caller then waits for the promise to resolve. 20 | */ 21 | return responsePromise; 22 | 23 | function closeModal(yesno: 'yes' | 'no') { 24 | if (modalWindow.classList) { 25 | modalWindow.classList.remove('is-active'); 26 | } 27 | 28 | modalWindow 29 | .querySelector('.modal-yes') 30 | .removeEventListener('click', listenerYes); 31 | modalWindow 32 | .querySelector('.modal-no') 33 | .removeEventListener('click', listenerNo); 34 | 35 | return resolve(yesno); 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /xx-final/src/lib/timer.ts: -------------------------------------------------------------------------------- 1 | import { showMessage } from './dom'; 2 | 3 | export const sayHelloTimer = function (ms: number) { 4 | let counter = 0; 5 | showMessage(`Starting the timer`, 'Response from Timer'); 6 | 7 | const callback = (ms: number) => { 8 | counter++; 9 | showMessage( 10 | `Hello every ${ms} milliseconds. (${counter} iterations)`, 11 | 'Response from Timer', 12 | true, 13 | ); 14 | 15 | if (counter === 5) { 16 | showMessage( 17 | `Goodbye. We said hello every ${ms} milliseconds. (after ${counter} iterations)`, 18 | 'Response from Timer', 19 | true, 20 | ); 21 | clearInterval(intervalId); 22 | } 23 | }; 24 | 25 | const intervalId = setInterval(callback, ms, ms); 26 | }; 27 | -------------------------------------------------------------------------------- /xx-final/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist/", 4 | "sourceMap": true, 5 | "noImplicitAny": true, 6 | // "module": "es6", 7 | "module": "commonjs", 8 | "target": "es5", 9 | // "allowJs": true, 10 | "allowSyntheticDefaultImports": true, 11 | "lib": ["es2018", "dom"] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /xx-final/webpack.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const path = require('path'); 3 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 4 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 5 | 6 | const index = 'index'; 7 | 8 | module.exports = { 9 | entry: `./src/${index}.ts`, 10 | devtool: 'inline-source-map', 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.tsx?$/, 15 | use: 'ts-loader', 16 | exclude: /node_modules/, 17 | }, 18 | { 19 | test: /\.s[ac]ss$/i, 20 | use: [ 21 | MiniCssExtractPlugin.loader, 22 | { 23 | loader: 'css-loader', 24 | }, 25 | { 26 | loader: 'sass-loader', 27 | options: { 28 | sourceMap: true, 29 | }, 30 | }, 31 | ], 32 | }, 33 | ], 34 | }, 35 | plugins: [ 36 | new MiniCssExtractPlugin({ 37 | filename: '[name].bundle.css', 38 | }), 39 | new CopyWebpackPlugin({ 40 | patterns: [ 41 | { 42 | from: `./${index}.html`, 43 | }, 44 | ], 45 | }), 46 | ], 47 | resolve: { 48 | extensions: ['.ts', '.js'], 49 | }, 50 | output: { 51 | filename: 'bundle.js', 52 | path: path.resolve(__dirname, 'dist'), 53 | }, 54 | performance: { 55 | maxEntrypointSize: 640000, 56 | maxAssetSize: 640000, 57 | }, 58 | devServer: { 59 | contentBase: 'dist', 60 | port: 8080, 61 | // Send API requests on localhost to API server get around CORS. 62 | proxy: { 63 | '/api': { 64 | target: { 65 | host: '0.0.0.0', 66 | protocol: 'http:', 67 | port: 8081, 68 | }, 69 | pathRewrite: { 70 | '^/api': '', 71 | }, 72 | }, 73 | }, 74 | }, 75 | }; 76 | --------------------------------------------------------------------------------