diff --git a/SUMMARY.md b/SUMMARY.md index 27b9a01c..0ac32e65 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -99,6 +99,7 @@ - [Week 3](courses/frontend/advanced-javascript/week3/README.md) - [Preparation](courses/frontend/advanced-javascript/week3/preparation.md) - [Session Plan](courses/frontend/advanced-javascript/week3/session-plan.md) + - [Exercises](courses/frontend/advanced-javascript/week3/session-materials/exercises.md) - [Assignment](courses/frontend/advanced-javascript/week3/assignment.md) - [Week 4](courses/frontend/advanced-javascript/week4/README.md) - [Preparation](courses/frontend/advanced-javascript/week4/preparation.md) diff --git a/courses/frontend/advanced-javascript/week2/assignment.md b/courses/frontend/advanced-javascript/week2/assignment.md index 85ea610b..836c04ca 100644 --- a/courses/frontend/advanced-javascript/week2/assignment.md +++ b/courses/frontend/advanced-javascript/week2/assignment.md @@ -11,25 +11,25 @@ The warmup is a **little abstract**, it will get more concrete later on! 1. Display the text `Called after 2.5 seconds` on the page 2.5 seconds after the script is loaded. 2. Create a function that takes 2 parameters: `delay` and `stringToLog`. Calling this function should display the `stringToLog` on the page after `delay` seconds. Call the function you have created with some different arguments. - ![second task](session-materials/carbon.png) + ![second task](./session-materials/carbon.png) 3. Create a button in html. When clicking this button, use the function you created in the previous task to display the text `Called after 5 seconds` on the page 5 seconds after the button is clicked. -![second task](session-materials/button-delay.gif) +![second task](./session-materials/button-delay.gif) 4. Create two functions and assign them to two different variables. One function displays `Earth` on the page, the other displays `Saturn`. Now create a new third function that has one parameter: `planetLogFunction`. The only thing the third function should do is call the provided parameter function. Try calling the third function once with the `Earth` function and once with the `Saturn` function. -![second task](session-materials/planet-log.png) +![second task](./session-materials/planet-log.png) 5. Create a button with the text "Log location". When this button is clicked, display the user's location (latitude, longitude) on the page using this [browser API](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation_API). -![second task](session-materials/log-location.gif) +![second task](./session-materials/log-location.gif) 6. _Optional_ Now show that location on a map using e.g. the [Google maps api](https://developers.google.com/maps/documentation/javascript/tutorial) 7. Create a function called `runAfterDelay`. It has two parameters: `delay` and `callback`. When called the function should wait `delay` seconds and then call the provided callback function. Add an input in the HTML for the delay (in seconds) and a button; when the button is clicked, read the delay from the input and call `runAfterDelay` with that delay and a callback that displays something on the page. -![second task](session-materials/run-after-delay.png) +![second task](./session-materials/run-after-delay.png) 8. Check if the user has double-clicked on the page. A double click is two clicks within 0.5 seconds. If a double click is detected, display the text "double click!" on the page. @@ -57,7 +57,7 @@ A user specifies how long time the game should be, and presses **"start game!"** Here is a gif of how the site should work: -![session material](session-materials/fastest-clicker.gif =400x) +![Fastest presser game demo](./session-materials/fastest-clicker.gif) You can implement it exactly like you want to, but here is my recommended order: diff --git a/courses/frontend/advanced-javascript/week3/README.md b/courses/frontend/advanced-javascript/week3/README.md index 430dbb24..3ffd5ab9 100644 --- a/courses/frontend/advanced-javascript/week3/README.md +++ b/courses/frontend/advanced-javascript/week3/README.md @@ -6,6 +6,7 @@ In this session, you'll learn how to write asynchronous code that is both effici - [Preparation](./preparation.md) - [Session Plan](./session-plan.md) (for mentors) +- [Exercises](./session-materials/exercises.md) - [Assignment](./assignment.md) ## Session Learning Goals diff --git a/courses/frontend/advanced-javascript/week3/assignment.md b/courses/frontend/advanced-javascript/week3/assignment.md index 84430345..d8948746 100644 --- a/courses/frontend/advanced-javascript/week3/assignment.md +++ b/courses/frontend/advanced-javascript/week3/assignment.md @@ -1,11 +1,13 @@ # Assignment -The assignment for this week is to build a currency calculator using [this API](https://open.er-api.com/v6/latest/USD) +The assignment for this week is to build a currency calculator using [this API](https://open.er-api.com/v6/latest/USD). + +Deliverable: a small browser application, so the user can interact with it and see the converted amount on the page. ## Technical specifications -1. Make a request to the API and store the Exchange rates as well as a list of currencies for the dropdowns. -2. User can enter an amount -3. User can choose a currency to convert from(default should be EUR) -4. User can choose a currency to convert to(Default should be DKK) -5. Whenever amount, currency from or currency to changes we show what the amount translates to in the to currency +1. Make a request to the API and use the response to obtain exchange rates and to populate the currency dropdowns. +2. The user can enter an amount. +3. The user can choose a currency to convert from (default: EUR). +4. The user can choose a currency to convert to (default: DKK). +5. When the amount, the "from" currency, or the "to" currency changes, show the equivalent amount in the "to" currency. diff --git a/courses/frontend/advanced-javascript/week3/session-materials/console-order.md b/courses/frontend/advanced-javascript/week3/session-materials/console-order.md new file mode 100644 index 00000000..9344678f --- /dev/null +++ b/courses/frontend/advanced-javascript/week3/session-materials/console-order.md @@ -0,0 +1,139 @@ +# Promise chaining – what is logged? + +Use these in class: show the code, ask “What will appear in the console, and in what order?”, then run it and compare. + +--- + +## Task 1 — basic: sync vs `.then` + +```js +console.log("A"); + +Promise.resolve().then(() => { + console.log("B"); +}); + +console.log("C"); +``` + +
+Answer + +Order: A, C, B + +Synchronous code runs first (A, then C). Callbacks passed to `.then` are scheduled as microtasks and run after the current script finishes, so B appears last. + +
+ +--- + +## Task 2 — values through the chain + +```js +Promise.resolve(1) + .then((x) => { + console.log(x); + return x + 1; + }) + .then((y) => { + console.log(y); + }); +``` + +
+Answer + +Logs: `1` then `2` + +Each `.then` receives the value returned by the previous handler. Returning a plain value wraps it in a resolved promise for the next step. + +
+ +--- + +## Task 3 — returning a Promise (flattening) + +```js +Promise.resolve("go") + .then((s) => { + console.log("a:", s); + return Promise.resolve("step"); + }) + .then((t) => { + console.log("b:", t); + }); +``` + +
+Answer + +Logs: `a: go` then `b: step` + +When a handler returns a Promise, the chain waits for it and passes its settled value to the next `.then` (the inner Promise is “flattened”). + +
+ +--- + +## Task 4 — rejection, skipped handlers, `.catch`, recovery + +```js +Promise.resolve() + .then(() => { + console.log("1"); + throw new Error("oops"); + }) + .then(() => { + console.log("2"); + }) + .catch(() => { + console.log("3"); + }) + .then(() => { + console.log("4"); + }); +``` + +
+Answer + +Logs: `1`, `3`, `4` + +The error skips the next `.then` (so `2` never runs). `.catch` handles the rejection; a successful `catch` returns a fulfilled promise, so the following `.then` still runs (`4`). + +
+ +--- + +## Task 5 — multiple `.catch` and `.then` in one chain + +```js +Promise.resolve() + .then(() => { + console.log("1"); + throw "first-error"; + }) + .catch((err) => { + console.log("catch-A", err); + return "recovered"; + }) + .then((value) => { + console.log("2", value); + throw "second-error"; + }) + .catch((err) => { + console.log("catch-B", err); + }) + .then(() => { + console.log("3"); + }); +``` + +
+Answer + +Logs: `1`, `catch-A first-error`, `2 recovered`, `catch-B second-error`, `3` + +The first `throw` is handled by `catch-A`, which returns `"recovered"`, so the chain continues fulfilled and `2` runs with that value. The next `throw` is handled by `catch-B`; a successful `catch` still yields a fulfilled promise, so the final `.then` runs (`3`). The second `.catch` never sees `first-error` because `catch-A` already handled it. + +
diff --git a/courses/frontend/advanced-javascript/week3/session-materials/demo/README.md b/courses/frontend/advanced-javascript/week3/session-materials/demo/README.md new file mode 100644 index 00000000..57b27aec --- /dev/null +++ b/courses/frontend/advanced-javascript/week3/session-materials/demo/README.md @@ -0,0 +1,45 @@ +# Mentors demo – Promises & `async`/`await` + +In-session live coding for **Week 3** (Advanced JavaScript). The demo walks through `fetch` with **JSONPlaceholder**, `async`/`await`, consuming promises with `.then()` / `.catch()`, creating promises with `new Promise`, `try` / `catch` with async code, `Promise.all`, and an optional promise microtask loop. You implement the worksheet during class; the solution file is the finished version. + +--- + +## Files in this folder + +| File | Purpose | +| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **index.js** | Worksheet: section banners, `// Task:` lines, and `// Next:` hints. Only `getUser` and `promiseLoop` are declared; you add the rest while teaching. Use this file when leading the session. | +| **index-solution.js** | Full implementation: `showOutput`, `getUser`, promise consumption, timed and pizza promises, `try` / `catch` fetch, `Promise.all`, and `promiseLoop`. | +| **index.html** | Minimal page that loads `index.js`. Add markup (e.g. `
`) when you want on-page output; the solution’s `showOutput` writes to `#out`.                                          |
+| **style.css**         | Basic layout and styles for `#out` (and `main` if you use it).                                                                                                                              |
+
+---
+
+## Where to find tasks and how they are marked
+
+Everything lives in **index.js**. Search for `// ==========` for section breaks, `// Task:` for what to build, and `// Next:` for suggested links to the trainee exercises.
+
+---
+
+## How the code works
+
+### URLs (JSONPlaceholder)
+
+- **`USER_URL`** – `https://jsonplaceholder.typicode.com/users/1`
+- **`POST_URL`** – `https://jsonplaceholder.typicode.com/posts/1`
+- **`TODO_URL`** – `https://jsonplaceholder.typicode.com/todos/1` (optional extra)
+
+### Solution-only helpers and functions
+
+- **`showOutput(text)`** – Sets `textContent` on `#out` when that element exists.
+- **`getUser()`** – `async` `fetch` of **`USER_URL`**, then `.json()`, then `showOutput` with stringified user data.
+- **`loadOneResourceWithThen()`** – Same resource with `.then` / `.catch` only (no `async`/`await`).
+- **`oneSecondMessage()`** – Promise that resolves after one second, then shows `"It worked"`.
+- **`demoOrderPizza()`** – Delayed resolve or reject, then shows pizza or error text.
+- **`getUserWithTryCatch()`** – Same fetch pattern as `getUser` with `try` / `catch` and errors on the page.
+- **`demoPromiseAll()`** – Fetches **`USER_URL`** and **`POST_URL`** in parallel, then shows a short two-line summary.
+- **`promiseLoop()`** – Schedules endless microtasks (illustration only; can freeze the tab if called).
+
+### Callback hell
+
+Not implemented in these files; use the session plan (e.g. npm `q` or your own example on the board).
diff --git a/courses/frontend/advanced-javascript/week3/session-materials/demo/index-solution.js b/courses/frontend/advanced-javascript/week3/session-materials/demo/index-solution.js
new file mode 100644
index 00000000..cd5cedab
--- /dev/null
+++ b/courses/frontend/advanced-javascript/week3/session-materials/demo/index-solution.js
@@ -0,0 +1,151 @@
+// Week 3 demo – Promises & async/await (solution)
+
+// JSONPlaceholder only (reliable in the browser). Session plan may mention Open Notify —
+// same async ideas, different URL.
+const USER_URL = "https://jsonplaceholder.typicode.com/users/1";
+const POST_URL = "https://jsonplaceholder.typicode.com/posts/1";
+const TODO_URL = "https://jsonplaceholder.typicode.com/todos/1";
+
+function showOutput(text) {
+  const el = document.getElementById("out");
+  if (el) {
+    el.textContent = text;
+  }
+}
+
+// =============================================================================
+// Async/await – simple usage
+// =============================================================================
+// Task: Load USER_URL with async/await
+
+async function getUser() {
+  const response = await fetch(USER_URL);
+  const user = await response.json();
+  showOutput(JSON.stringify(user, null, 2));
+}
+
+// Next: Exercise 1
+
+// =============================================================================
+// Why use Promises? :: Callback Hell
+// =============================================================================
+// Show Callback Hell example in https://www.npmjs.com/package/q
+
+// =============================================================================
+// Promise consumption
+// =============================================================================
+// Task: Load one of the resources (e.g. USER_URL); show success or error on the page using .then / .catch only.
+
+function loadOneResourceWithThen() {
+  showOutput("Loading…");
+  fetch(USER_URL)
+    .then((response) => response.json())
+    .then((data) => {
+      showOutput(JSON.stringify(data, null, 2));
+    })
+    .catch((error) => {
+      showOutput(String(error));
+    });
+}
+
+// Next: Chaining examples
+// Next: Exercise 2
+
+// =============================================================================
+// Promise creation
+// =============================================================================
+// Task: Create a Promise that resolves after 1 second and shows "It worked" on the page.
+// Task: Create demoOrderPizza: a pizza-order Promise — after a 'baking' delay it either resolves with a pizza you can eat (show that on the page) or rejects if baking failed (show the failure on the page).
+
+function oneSecondMessage() {
+  showOutput("…");
+  const oneSecondTimeoutPromise = new Promise((resolve) => {
+    setTimeout(() => {
+      resolve();
+    }, 1000);
+  });
+
+  oneSecondTimeoutPromise.then(() => {
+    showOutput("It worked");
+  });
+}
+
+function demoOrderPizza() {
+  showOutput("Baking… (3s for demo)");
+  const pizzaMakingTime = 3000;
+  const didPizzaBakingSucceed = true;
+  const pizza = "Macaroni pizza";
+
+  const orderPizzaPromise = new Promise((resolve, reject) => {
+    setTimeout(() => {
+      if (didPizzaBakingSucceed) {
+        resolve(pizza);
+      } else {
+        reject("The pizza was a mess");
+      }
+    }, pizzaMakingTime);
+  });
+
+  orderPizzaPromise
+    .then((p) => {
+      showOutput(`Let's eat the ${p}`);
+    })
+    .catch((error) => {
+      showOutput(`Let's eat nothing: ${error}`);
+    });
+}
+
+// Next: Exercise 3
+// Next: Exercise 4
+
+// =============================================================================
+// Back to async/await (try / catch)
+// =============================================================================
+// Task: improve getUser to use try/catch to handle errors and show the error on the page.
+
+async function getUserWithTryCatch() {
+  try {
+    const response = await fetch(USER_URL);
+    const user = await response.json();
+    showOutput(JSON.stringify(user, null, 2));
+  } catch (err) {
+    showOutput(String(err));
+  }
+}
+
+// Next: Exercise 5
+
+// =============================================================================
+// Promise.all
+// =============================================================================
+
+async function demoPromiseAll() {
+  showOutput("Loading both…");
+  try {
+    const [userRes, postRes] = await Promise.all([
+      fetch(USER_URL),
+      fetch(POST_URL),
+    ]);
+    const [user, post] = await Promise.all([userRes.json(), postRes.json()]);
+    const summary = [
+      "User: " + user.name + " (" + user.email + ")",
+      "Post: " + post.title,
+    ].join("\n");
+    showOutput(summary);
+  } catch (e) {
+    showOutput(String(e));
+  }
+}
+
+// Next: Exercise 6
+
+// =============================================================================
+// (Optional) Infinite loop via Promises
+// =============================================================================
+
+function promiseLoop() {
+  return Promise.resolve().then(() => {
+    console.log("tick");
+    return promiseLoop();
+  });
+}
diff --git a/courses/frontend/advanced-javascript/week3/session-materials/demo/index.html b/courses/frontend/advanced-javascript/week3/session-materials/demo/index.html
new file mode 100644
index 00000000..cae179b8
--- /dev/null
+++ b/courses/frontend/advanced-javascript/week3/session-materials/demo/index.html
@@ -0,0 +1,12 @@
+
+
+  
+    
+    
+    Demo – Week 3
+    
+  
+  
+    
+  
+
diff --git a/courses/frontend/advanced-javascript/week3/session-materials/demo/index.js b/courses/frontend/advanced-javascript/week3/session-materials/demo/index.js
new file mode 100644
index 00000000..be2447e4
--- /dev/null
+++ b/courses/frontend/advanced-javascript/week3/session-materials/demo/index.js
@@ -0,0 +1,62 @@
+// Week 3 demo – Promises & async/await (worksheet for class)
+
+// JSONPlaceholder only (reliable in the browser). Session plan may mention Open Notify —
+// same async ideas, different URL.
+const USER_URL = "https://jsonplaceholder.typicode.com/users/1";
+const POST_URL = "https://jsonplaceholder.typicode.com/posts/1";
+const TODO_URL = "https://jsonplaceholder.typicode.com/todos/1";
+
+// =============================================================================
+// Async/await – simple usage
+// =============================================================================
+// Task: Load USER_URL with async/await
+
+async function getUser() {}
+
+// Next: Exercise 1
+
+// =============================================================================
+// Why use Promises? :: Callback Hell
+// =============================================================================
+// Show Callback Hell example in https://www.npmjs.com/package/q
+
+// =============================================================================
+// Promise consumption
+// =============================================================================
+// Task: Load one of the resources (e.g. USER_URL); show success or error on the page using .then / .catch only.
+
+// Next: Chaining examples
+// Next: Exercise 2
+
+// =============================================================================
+// Promise creation
+// =============================================================================
+// Task: Create a Promise that resolves after 1 second and shows "It worked" on the page.
+// Task: Create demoOrderPizza: a pizza-order Promise — after a 'baking' delay it either resolves with a pizza you can eat (show that on the page) or rejects if baking failed (show the failure on the page).
+
+// Next: Exercise 3
+// Next: Exercise 4
+
+// =============================================================================
+// Back to async/await (try / catch)
+// =============================================================================
+// Task: improve getUser to use try/catch to handle errors and show the error on the page.
+
+// Next: Exercise 5
+
+// =============================================================================
+// Promise.all
+// =============================================================================
+
+// Next: Exercise 6
+
+// =============================================================================
+// (Optional) Infinite loop via Promises
+// =============================================================================
+
+function promiseLoop() {
+  return Promise.resolve().then(() => {
+    console.log("tick");
+    return promiseLoop();
+  });
+}
diff --git a/courses/frontend/advanced-javascript/week3/session-materials/demo/style.css b/courses/frontend/advanced-javascript/week3/session-materials/demo/style.css
new file mode 100644
index 00000000..ab055fd9
--- /dev/null
+++ b/courses/frontend/advanced-javascript/week3/session-materials/demo/style.css
@@ -0,0 +1,34 @@
+/* Week 3 demo – minimal */
+
+*,
+*::before,
+*::after {
+  box-sizing: border-box;
+}
+
+body {
+  margin: 0;
+  font-family: system-ui, sans-serif;
+  line-height: 1.5;
+}
+
+main {
+  max-width: 40rem;
+  margin: 0 auto;
+  padding: 1rem;
+}
+
+#out {
+  margin: 0.25rem 0 1rem;
+  padding: 0.5rem;
+  border: 1px solid #ccc;
+  font-family: ui-monospace, monospace;
+  font-size: 0.8rem;
+  white-space: pre-wrap;
+  word-break: break-word;
+  min-height: 1.25rem;
+}
+
+#out:empty {
+  display: none;
+}
diff --git a/courses/frontend/advanced-javascript/week3/session-materials/event-loop-demo/event-loop.css b/courses/frontend/advanced-javascript/week3/session-materials/event-loop-demo/event-loop.css
new file mode 100644
index 00000000..65914c0f
--- /dev/null
+++ b/courses/frontend/advanced-javascript/week3/session-materials/event-loop-demo/event-loop.css
@@ -0,0 +1,330 @@
+/* Event loop + microtasks visualization – light theme */
+
+*,
+*::before,
+*::after {
+  box-sizing: border-box;
+}
+
+body {
+  margin: 0;
+  font-family:
+    system-ui,
+    -apple-system,
+    sans-serif;
+  line-height: 1.5;
+  background: #f5f5f5;
+  color: #222;
+}
+
+main {
+  max-width: 72rem;
+  margin: 0 auto;
+  padding: 1.5rem;
+}
+
+h1 {
+  margin: 0 0 0.5rem;
+  font-size: 1.5rem;
+  color: #1967d2;
+}
+
+.intro {
+  margin: 0 0 1.25rem;
+  font-size: 0.9rem;
+  color: #5f6368;
+  max-width: 52rem;
+}
+
+.intro code {
+  font-size: 0.85em;
+  padding: 0.1em 0.35em;
+  background: #e8eaed;
+  border-radius: 4px;
+}
+
+/* Code section – blocks at the start */
+.code-section {
+  margin-bottom: 1.5rem;
+}
+
+.section-label {
+  font-weight: 600;
+  font-size: 0.875rem;
+  margin-bottom: 0.5rem;
+  color: #5f6368;
+}
+
+/* Tooltip: hover on ? to show definition */
+.tooltip-trigger {
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  width: 1.1em;
+  height: 1.1em;
+  margin-left: 0.25em;
+  font-size: 0.85em;
+  font-weight: 700;
+  line-height: 1;
+  color: #5f6368;
+  background: #e8eaed;
+  border-radius: 50%;
+  cursor: help;
+  position: relative;
+  vertical-align: middle;
+}
+
+.tooltip-trigger:hover,
+.tooltip-trigger:focus {
+  color: #1967d2;
+  background: #e8f0fe;
+  outline: none;
+}
+
+.tooltip {
+  position: absolute;
+  left: 50%;
+  bottom: calc(100% + 0.5em);
+  transform: translateX(-50%);
+  width: 18em;
+  max-width: 90vw;
+  padding: 0.6em 0.75em;
+  font-size: 0.8rem;
+  font-weight: 400;
+  line-height: 1.4;
+  color: #222;
+  background: #fff;
+  border: 1px solid #dadce0;
+  border-radius: 6px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
+  white-space: normal;
+  visibility: hidden;
+  opacity: 0;
+  transition:
+    opacity 0.15s ease,
+    visibility 0.15s ease;
+  z-index: 20;
+  pointer-events: none;
+}
+
+.tooltip code {
+  font-size: 0.9em;
+}
+
+.tooltip-trigger:hover .tooltip,
+.tooltip-trigger:focus .tooltip {
+  visibility: visible;
+  opacity: 1;
+}
+
+.tooltip::after {
+  content: "";
+  position: absolute;
+  top: 100%;
+  left: 50%;
+  margin-left: -6px;
+  border: 6px solid transparent;
+  border-top-color: #dadce0;
+}
+
+.code-blocks {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 0.5rem;
+  min-height: 3rem;
+  padding: 0.75rem;
+  background: #fff;
+  border: 1px solid #dadce0;
+  border-radius: 8px;
+}
+
+.code-block,
+.task {
+  cursor: grab;
+}
+
+.code-block:active,
+.task:active {
+  cursor: grabbing;
+}
+
+.code-block.dragging,
+.task.dragging {
+  opacity: 0.6;
+}
+
+.code-block {
+  padding: 0.4rem 0.75rem;
+  border-radius: 4px;
+  font-size: 0.8rem;
+  font-family: ui-monospace, monospace;
+  font-weight: 500;
+  transition: opacity 0.3s ease;
+}
+
+.code-block.sync {
+  background: #e8f0fe;
+  color: #1967d2;
+  border: 1px solid #aecbfa;
+}
+
+.code-block.timeout,
+.code-block.timeout-1 {
+  background: #e6f4ea;
+  color: #137333;
+  border: 1px solid #81c995;
+}
+
+.code-block.timeout-2 {
+  background: #fef7e0;
+  color: #b06000;
+  border: 1px solid #f9d57e;
+}
+
+.code-block.timeout-0 {
+  background: #e8e0ec;
+  color: #7c4dff;
+  border: 1px solid #b39ddb;
+}
+
+.code-block.event {
+  background: #f1f3f4;
+  color: #5f6368;
+  border: 1px solid #dadce0;
+}
+
+/* Promise → microtask queue (same colour in every zone) */
+.code-block.promise-then,
+.code-block.promise-catch {
+  background: #fce8e6;
+  color: #c5221f;
+  border: 1px solid #f5aea8;
+}
+
+.diagram {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 1rem;
+  align-items: stretch;
+  min-height: 10rem;
+  margin-bottom: 1.5rem;
+}
+
+.zone {
+  flex: 1 1 14rem;
+  border: 2px solid #dadce0;
+  border-radius: 8px;
+  padding: 0.75rem;
+  display: flex;
+  flex-direction: column;
+  background: #fff;
+}
+
+.zone-label {
+  font-weight: 600;
+  font-size: 0.875rem;
+  margin-bottom: 0.5rem;
+  color: #5f6368;
+}
+
+.call-stack .zone-label {
+  color: #1967d2;
+}
+
+.microtask-queue .zone-label {
+  color: #c5221f;
+}
+
+.timer .zone-label {
+  color: #b06000;
+}
+
+.async-queue .zone-label {
+  color: #137333;
+}
+
+.zone-tasks {
+  flex: 1;
+  display: flex;
+  flex-wrap: wrap;
+  gap: 0.5rem;
+  align-content: flex-start;
+  align-items: center;
+  min-height: 3.5rem;
+}
+
+.task {
+  padding: 0.35rem 0.6rem;
+  border-radius: 4px;
+  font-size: 0.8rem;
+  font-family: ui-monospace, monospace;
+  font-weight: 500;
+  transition: opacity 0.3s ease;
+}
+
+.task.sync {
+  background: #e8f0fe;
+  color: #1967d2;
+  border: 1px solid #aecbfa;
+}
+
+.task.timeout-1 {
+  background: #e6f4ea;
+  color: #137333;
+  border: 1px solid #81c995;
+}
+
+.task.timeout-2 {
+  background: #fef7e0;
+  color: #b06000;
+  border: 1px solid #f9d57e;
+}
+
+.task.timeout-0 {
+  background: #e8e0ec;
+  color: #7c4dff;
+  border: 1px solid #b39ddb;
+}
+
+.task.event {
+  background: #f1f3f4;
+  color: #5f6368;
+  border: 1px solid #dadce0;
+}
+
+.task.promise-then,
+.task.promise-catch {
+  background: #fce8e6;
+  color: #c5221f;
+  border: 1px solid #f5aea8;
+}
+
+.controls {
+  display: flex;
+  gap: 0.5rem;
+}
+
+.controls button {
+  padding: 0.5rem 1rem;
+  font-size: 0.875rem;
+  border: none;
+  border-radius: 6px;
+  cursor: pointer;
+  background: #1967d2;
+  color: #fff;
+  font-weight: 500;
+}
+
+.controls button:hover {
+  background: #1557b0;
+}
+
+.controls button#btn-reset {
+  background: #fff;
+  color: #5f6368;
+  border: 1px solid #dadce0;
+}
+
+.controls button#btn-reset:hover {
+  background: #f1f3f4;
+}
diff --git a/courses/frontend/advanced-javascript/week3/session-materials/event-loop-demo/event-loop.html b/courses/frontend/advanced-javascript/week3/session-materials/event-loop-demo/event-loop.html
new file mode 100644
index 00000000..75f112e1
--- /dev/null
+++ b/courses/frontend/advanced-javascript/week3/session-materials/event-loop-demo/event-loop.html
@@ -0,0 +1,97 @@
+
+
+  
+    
+    
+    Event loop & microtasks – Week 3
+    
+  
+  
+    
+

Event loop & microtasks

+

+ Drag blocks where they belong. Same idea as week 2, with one extra box: + work from a Promise (like .then / + .catch) waits in the microtask queue and + runs before the next setTimeout + callback. +

+ +
+ +
+
+ +
+
+
+ Call stack + + ? + Where normal synchronous code runs. When it finishes, + microtasks run, then one callback from the task queue. + +
+
+
+
+
+ Microtask queue + + ? + When a promise settles, the engine queues its + .then / .catch work here. This queue + is emptied before the next setTimeout (or other + task-queue) callback. + +
+
+
+
+
+ Task queue + + ? + Callbacks waiting to run after microtasks — for example from + setTimeout. + +
+
+
+
+
+ Timer (Web APIs) + + ? + When you call setTimeout(fn, ms), the timer waits, + then the callback is queued as a macrotask. Part of the + browser’s Web APIs. + +
+
+
+
+ +
+ +
+
+ + + diff --git a/courses/frontend/advanced-javascript/week3/session-materials/event-loop-demo/event-loop.js b/courses/frontend/advanced-javascript/week3/session-materials/event-loop-demo/event-loop.js new file mode 100644 index 00000000..4505037c --- /dev/null +++ b/courses/frontend/advanced-javascript/week3/session-materials/event-loop-demo/event-loop.js @@ -0,0 +1,104 @@ +// Event loop: draggable blocks → Call stack, Microtask queue, Task queue, Timer +// setTimeout: full label in Timer (registration), "fn" elsewhere (the callback) — like week 2. +// Sync and Promise blocks always keep their full label. + +var codeBlocks = document.getElementById("code-blocks"); +var callstackTasks = document.getElementById("callstack-tasks"); +var microtaskTasks = document.getElementById("microtask-tasks"); +var timerTasks = document.getElementById("timer-tasks"); +var asyncTasks = document.getElementById("async-tasks"); +var btnReset = document.getElementById("btn-reset"); + +// Same shape as week 2, plus two promise blocks for the microtask queue. +var INITIAL_BLOCKS = [ + { label: "sync 1", type: "sync" }, + { label: "sync 2", type: "sync" }, + { label: "setTimeout(fn, 2s)", type: "timeout-1" }, + { label: "sync 3", type: "sync" }, + { label: "setTimeout(fn, 0)", type: "timeout-0" }, + { label: "sync 4", type: "sync" }, + { label: "Promise .then(fn)", type: "promise-then" }, + { label: "setTimeout(fn, 4s)", type: "timeout-2" }, + { label: "sync 5", type: "sync" }, + { label: "Promise .catch(fn)", type: "promise-catch" }, +]; + +function createBlock(label, type, isInCodeArea) { + var el = document.createElement("div"); + el.draggable = true; + el.dataset.type = type; + el.dataset.originalLabel = label; + el.textContent = label; + el.className = isInCodeArea ? "code-block " + type : "task " + type; + el.addEventListener("dragstart", onDragStart); + return el; +} + +function isTimeoutType(type) { + return type === "timeout-0" || type === "timeout-1" || type === "timeout-2"; +} + +function onDragStart(ev) { + ev.dataTransfer.effectAllowed = "move"; + ev.dataTransfer.setData("text/plain", ""); + ev.target.classList.add("dragging"); +} + +function onDragEnd(ev) { + ev.target.classList.remove("dragging"); +} + +function makeDropZone(zoneEl, isCodeArea, zoneKind) { + zoneEl.addEventListener("dragover", function (ev) { + ev.preventDefault(); + ev.dataTransfer.dropEffect = "move"; + }); + zoneEl.addEventListener("drop", function (ev) { + ev.preventDefault(); + var dragEl = document.querySelector(".dragging"); + if (!dragEl || zoneEl.contains(dragEl)) return; + zoneEl.appendChild(dragEl); + if (isCodeArea) { + dragEl.className = "code-block " + (dragEl.dataset.type || "sync"); + dragEl.textContent = dragEl.dataset.originalLabel || dragEl.textContent; + } else { + dragEl.className = "task " + (dragEl.dataset.type || "sync"); + if (zoneKind === "timer") { + dragEl.textContent = dragEl.dataset.originalLabel || dragEl.textContent; + } else if (isTimeoutType(dragEl.dataset.type || "")) { + dragEl.textContent = "fn"; + } + } + }); +} + +function setupDropZones() { + makeDropZone(codeBlocks, true, "code"); + makeDropZone(callstackTasks, false, "callstack"); + makeDropZone(microtaskTasks, false, "microtask"); + makeDropZone(asyncTasks, false, "async"); + makeDropZone(timerTasks, false, "timer"); +} + +function generateCodeBlocks() { + codeBlocks.innerHTML = ""; + INITIAL_BLOCKS.forEach(function (item, i) { + var block = createBlock(item.label, item.type, true); + block.id = "block-" + i; + block.addEventListener("dragend", onDragEnd); + codeBlocks.appendChild(block); + }); +} + +function reset() { + callstackTasks.innerHTML = ""; + microtaskTasks.innerHTML = ""; + timerTasks.innerHTML = ""; + asyncTasks.innerHTML = ""; + generateCodeBlocks(); +} + +setupDropZones(); +generateCodeBlocks(); + +btnReset.addEventListener("click", reset); diff --git a/courses/frontend/advanced-javascript/week3/session-materials/exercises.md b/courses/frontend/advanced-javascript/week3/session-materials/exercises.md new file mode 100644 index 00000000..056292b2 --- /dev/null +++ b/courses/frontend/advanced-javascript/week3/session-materials/exercises.md @@ -0,0 +1,54 @@ +# Exercises + +Work through these in order. + +## Exercise 1 + +Using async await + +1. `fetch` yes or no from this api: `https://yesno.wtf/api`. Show the answer on the page. + +## Exercise 2 + +Using promises + +1. `fetch` yes or no from this api: `https://yesno.wtf/api`. Show the answer on the page. +2. Try fetching a url that rejects e.g. `https://knajskdskj.jasdk`. Show the error message on the page. + +## Exercise 3 + +1. Create a promise that resolves after 4 seconds. Use this promise to show the text `hello` on the page after 4 seconds. +2. Now make the promise fail by rejecting it with an error message instead of resolving it, and show the error message on the page. + +## Exercise 4 + +Create a function that returns a promise, that you can use like this: + +```js +// YesNoFail4Seconds should wait 4 seconds before it does one of the following 3 things: +// resolves with a yes +// resolves with a no +// or rejects +// Look into Math.random() +YesNoFail4Seconds() + .then((data) => { + // Show on the page: The answer is ${data} + }) + .catch((error) => { + // Show on the page: the error + }); +``` + +The above example show how to consume the promise using promises. Now try consume the `YesNoFail4Seconds` using async/await + +## Exercise 5 + +Using async await + +1. Fetch a user from JSONPlaceholder (for example `https://jsonplaceholder.typicode.com/users/1`) +2. After that succeeds, fetch movies using [this api](https://gist.githubusercontent.com/pankaj28843/08f397fcea7c760a99206bcb0ae8d0a4/raw/02d8bc9ec9a73e463b13c44df77a87255def5ab9/movies.json) +3. Show the movies on the page + +## Exercise 6 + +Get the JSONPlaceholder user and the movies at the same time. Show the movies and the battery status on the page when the related promises have resolved. diff --git a/courses/frontend/advanced-javascript/week3/session-plan.md b/courses/frontend/advanced-javascript/week3/session-plan.md index 8af8c832..02400cb2 100644 --- a/courses/frontend/advanced-javascript/week3/session-plan.md +++ b/courses/frontend/advanced-javascript/week3/session-plan.md @@ -7,6 +7,8 @@ These are some examples of previously created materials by mentors that you can use yourself, or for inspiration. - [Notion Page Handout](https://dandy-birth-1b2.notion.site/HYF-Aarhus-JS-3-Week-2-0287dd1293df4a0a92171e62ce12f5c8?pvs=4) (by [Thomas](https://github.com/te-online)) +- [Demo](./session-materials/demo/) – In-session live coding from **Code inspiration** below (not the trainee exercises). **index.js** = worksheet stubs; **index-solution.js** = reference. [README](./session-materials/demo/README.md). +- [Event loop demo](./session-materials/event-loop-demo/event-loop.html) – Drag-and-drop diagram; week 2 version plus **microtask queue** and promise blocks. ## Session Outline @@ -20,78 +22,29 @@ First when they fully understand one part of promises, I move on! Don't over-com - Quickly recap asynchronicity - Ask the trainees what it means that some code is asynchronous - Practical example of async/await - - [Exercises 1](#exercise-1) + - [Exercises 1](./session-materials/exercises.md#exercise-1) - Promise - Why do we use promises? - So important to explain this, the trainees always ask this! [Is there specific functionality that can only be done with promises in JS?](https://stackoverflow.com/questions/39004567/why-do-we-need-promise-in-js) - Consumption - [Code inspiration](#promise-consumption) - Example, call some function that returns a promise (like fetch) - - [Exercises 2](#exercise-2) + - [Exercises 2](./session-materials/exercises.md#exercise-2) - Creation - [Code inspiration](#promise-creation) - - [Exercises 3](#exercise-3) and then [Exercises 4](#exercise-4) + - [Exercises 3](./session-materials/exercises.md#exercise-3) and then [Exercises 4](./session-materials/exercises.md#exercise-4) - Async await - - [Exercises 5](#exercise-5) + - [Exercises 5](./session-materials/exercises.md#exercise-5) - `Promise.all` - Let trainees investigate - Optional - Chaining. Calling `.then` returns a promise. Only get to here when they understand async/await and promise consumption and creation. - [Reason for promise](https://mobile.twitter.com/addyosmani/status/1097035418657144832?s=19) - - [Exercises 5](#exercise-5) and [Exercises 6](#exercise-6) + - [Exercises 5](./session-materials/exercises.md#exercise-5) and [Exercises 6](./session-materials/exercises.md#exercise-6) ## Exercises - +See [Exercises](./session-materials/exercises.md). Trainees show results on the page (update the DOM), not in the console. -### Exercise 1 - -Using async await - -1. `fetch` yes or no from this api: `https://yesno.wtf/api`. log out the answer - -### Exercise 2 - -Using promises - -1. `fetch` yes or no from this api: `https://yesno.wtf/api`. log out the answer -2. Try fetching a url that rejects e.g. `https://knajskdskj.jasdk`. Log out the error message - -### Exercise 3 - -1. Create a promise that resolves after 4 seconds. Use this promise to log out the text 'hello' after 4 seconds. -2. Now make the promise fail by rejecting it with an error message instead of resolving it, and log the error message to the console. - -### Exercise 4 - -Create a function that returns a promise, that you can use like this: - -```js -// YesNoFail4Seconds should wait 4 seconds before it does one of the following 3 things: -// resolves with a yes -// resolves with a no -// or rejects -// Look into Math.random() -YesNoFail4Seconds() - .then((data) => { - console.log(`The answer is ${data}`); - }) - .catch((error) => { - console.log(error); - }); -``` - -The above example show how to consume the promise using promises. Now try consume the `YesNoFail4Seconds` using async/await - -### Exercise 5 - -Using async await - -1. Fetch the astronauts -2. After the astronauts has been fetched, fetch movies using [this api](https://gist.githubusercontent.com/pankaj28843/08f397fcea7c760a99206bcb0ae8d0a4/raw/02d8bc9ec9a73e463b13c44df77a87255def5ab9/movies.json) -3. Log out the movies - -### Exercise 6 - -Get the astronauts and the movies at the same time. Log out the movies and the battery status when both promises has been resolved. +[Console order](./session-materials/console-order.md) – Optional “what is logged?” promise chaining. ## Code inspiration @@ -108,22 +61,21 @@ Get the astronauts and the movies at the same time. Log out the movies and the b // await waits until we have fetched the data from the api. Or said in another way await waits until fetch has resolved with the data from the api // write async before a function for await to work. What does it mean that something is asynchronous? -async function getAstronauts() { +// JSONPlaceholder works reliably in the browser (same idea as Open Notify / astronauts, different URL). +async function getJsonPlaceholderUser() { // await waits until we have data from fetch before it runs the next line. No need for callbacks 🤯 console.log("Before we fetch data"); - const astronautsResponse = await fetch( - "http://api.open-notify.org/astros.json", - ); + const response = await fetch("https://jsonplaceholder.typicode.com/users/1"); console.log( "This is logged out after some time, even though the code looks synchronous! 🤯", ); - const astronauts = await astronautsResponse.json(); + const user = await response.json(); console.log("This is logged out after some time! 🤯"); - console.log(astronauts); - return astronauts; + console.log(user); + return user; } -getAstronauts(); +getJsonPlaceholderUser(); ``` ### Promise consumption @@ -138,10 +90,10 @@ The trainees should be able to answer these questions: // How would you explain your mom what resolved and rejected means? ```js -fetch("http://api.open-notify.org/astros.json") - .then((astronautsResponse) => astronautsResponse.json()) - .then((astronauts) => { - console.log(astronauts); +fetch("https://jsonplaceholder.typicode.com/users/1") + .then((response) => response.json()) + .then((user) => { + console.log(user); }) .catch((error) => console.log(error)); @@ -215,19 +167,19 @@ console.log(test()); So writing `async` in front of a function makes it return a promise! The keyword `await` makes JavaScript wait until that promise resolved and returns its result. ```js -async function getAstronauts() { +async function getJsonPlaceholderUserSafe() { try { - const astronautsResponse = await fetch( - "http://api.open-notify.org/astros.json", + const response = await fetch( + "https://jsonplaceholder.typicode.com/users/1", ); - const astronauts = await astronautsResponse.json(); - return astronauts; + const user = await response.json(); + return user; } catch (err) { - throw "Fetching the astronauts went wrong"; + throw "Fetching the user went wrong"; } } -const astronauts = getAstronauts(); +const userPromise = getJsonPlaceholderUserSafe(); ``` ### Function that returns a promise