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.
- 
+ 
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.
-
+
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.
-
+
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).
-
+
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.
-
+
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:
-
+
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.
+
+
+
+
Code
+
+
+
+
+
+
+ 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