Promises, Interfaces, and the Socratic Tutor
In this lab, you will build the same e-commerce dashboard simulator as Lab 2 (promise-based API simulators, a chained promise workflow, and custom error classes), while using Claude as a Socratic Tutor: an AI that asks you guiding questions but never writes code for you. Working this way builds two professional skills at once: fluency with asynchronous TypeScript, and the discipline to use AI tools in ways that strengthen, rather than replace, your own reasoning.
Scenario
You are developing an e-commerce dashboard that fetches data from various APIs, including a product catalog, user reviews, and a sales report. Each API call could potentially fail due to network issues, incorrect endpoints, or data inconsistencies. To build a stable and user-friendly application, you need to manage these scenarios with proper error handling.
Your team has also adopted an AI usage policy for developers in training: AI assistants may coach you with questions, but every line of code must be your own. Your tech lead wants to see both your working code and evidence of how you reasoned through the design.
Learning Objectives
By the end of this lab, you will be able to:
- Design TypeScript interfaces for API data by reasoning through guided questions instead of copying answers.
- Apply Promises to manage multiple asynchronous operations, including chaining and dependencies between calls.
- Utilize
.catch()and.finally()to handle errors and perform cleanup tasks in a Promise chain. - Design custom error classes to improve error identification and debugging.
- Manually refactor promise-based code to
async/awaitwithtry/catch, and explain the transition in your own words. - Direct an AI assistant to hold a Socratic Tutor role: questions only, no generated code.
Time Allotment
- Time: 2.5 hours (asynchronous)
AI Usage Policy for This Lab
In this lab, Claude acts as a Socratic Tutor. It may ask you questions, respond to your reasoning, and challenge your assumptions. It may not write, complete, or correct code for you, including type annotations and small fragments. Every line of code you submit must be written by you; the only exception is the starter code printed in this lab page, which you may copy as given. If Claude produces code despite your instructions, do not use it: remind Claude of its role and continue. Submissions containing AI-written code will be returned as incomplete.
Instructions
Part 1: Project and AI Setup (15 minutes)
Step 1: Create the Project
-
Create a new project folder and initialize it:
mkdir promise-dashboard cd promise-dashboard npm init -y npm install typescript @types/node --save-dev mkdir src -
Create a
tsconfig.jsonfile in the project root with the following contents, so that compiled output lands in adistfolder. This is the CommonJS configuration introduced in Required Reading 1, with a newertargetbecause.finally()requires ES2018 or later. Create the file manually rather than runningnpx tsc --init: recent versions of that command generate a configuration whose defaults ("module": "nodenext"with"verbatimModuleSyntax": true) will not compile theimport/exportstyle used in this lab.{ "compilerOptions": { "target": "ES2020", "module": "commonjs", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "outDir": "./dist", "rootDir": "./src" }, "include": ["src/**/*.ts"], "exclude": ["node_modules"] } -
Create four empty files inside
src. Your project should look like this:promise-dashboard/ ├── src/ │ ├── apiSimulator.ts │ ├── errors.ts │ ├── index.ts │ └── types.ts ├── package.json └── tsconfig.json -
Initialize a Git repository and add a
.gitignorefile containing at least:node_modules/ dist/
Step 2: Set Up Your Socratic Tutor
Choose one of the two paths below. Path A is recommended for this lab because the entire AI interaction is conversational.
Path A: claude.ai (recommended)
-
Sign in at https://claude.ai with your Per Scholas-provided Claude Pro account.
-
Create a new Project (a Pro feature) named
Socratic Tutor: TypeScript Lab. A Project lets you set custom instructions that apply to every conversation inside it, which is the most reliable way to keep Claude in the tutor role for the whole lab. -
Paste the following primer into the Project’s custom instructions. If you prefer not to use a Project, paste the same primer as the first message of a new conversation instead:
You are acting as a Socratic tutor for a TypeScript lab on interfaces, Promises, and error handling. Follow these rules for this entire conversation: 1. Do not write, edit, complete, or correct code under any circumstances. This includes code fragments, type annotations, interface definitions, and "fixed" versions of any code I share with you. 2. Ask me one guiding question at a time, then wait for my answer before continuing. 3. Before moving to a new topic, confirm that my reasoning is sound, or challenge it with a follow-up question if it is not. 4. If I ask you for code or for a direct answer, decline and respond with a question that leads me toward finding it myself. -
Start a new conversation inside the Project and confirm the role is active by sending:
What are your rules for this session?Claude should restate the rules without offering to write code.
Path B: Claude Code (terminal)
-
If Claude Code is not already installed, install it globally (Node.js 18 or later is required, which you already have):
npm install -g @anthropic-ai/claude-code -
Create a
CLAUDE.mdfile in the project root. Claude Code reads this file at the start of every session, so it enforces the tutor role automatically:# Socratic Tutor Rules You are a Socratic tutor for this TypeScript lab. Follow these rules in every session: - Do not write, edit, or generate code. You may read the files in this project, but never modify them. - Ask one guiding question at a time and wait for my answer. - Confirm my reasoning before advancing, or challenge it with a follow-up question. - If I ask for code or a direct answer, decline and ask a question instead. -
Run
claudeinside the project directory, and use/loginto sign in with your Per Scholas-provided account if prompted. -
Claude Code asks permission before editing files or running commands. For this lab, decline any request to edit files. The agent may read your code, but it must never modify it.
If you do not have access to a Pro-level Claude account through Per Scholas, you may complete this lab using the free tier of Claude or another AI assistant with equivalent capabilities. Free-tier usage limits may require you to complete the lab across more than one session. If you use the free tier, paste the primer at the start of each new conversation, and copy important excerpts of the dialogue into your REFLECTION.md as you go, since a long session may reach its limit before you can generate a share link.
Step 3: Create Your Reflection File
Create a REFLECTION.md file in the project root with this starter template. You will fill it in during the lab:
# Lab 2 (AI) Reflection
## Socratic Conversation
Shared conversation link: (paste link here)
If sharing is unavailable, paste transcript excerpts below instead.
## Checkpoint 1: Two Questions That Changed My Design
1.
2.
## From Promises to Async/Await (150 to 250 words, written by me)Part 2: Socratic Interface Design (30 minutes)
Before writing any implementation code, you will design the TypeScript interfaces for the dashboard’s data. Claude will guide you there with questions; you will write every line of the interfaces yourself.
-
Open the dialogue. Send Claude the following message, which contains the mock data shapes your dashboard will work with:
Here is the mock data my e-commerce dashboard will work with. Product catalog entries: [ { "id": 1, "name": "Laptop", "price": 1200 }, { "id": 2, "name": "Headphones", "price": 200 } ] Review entries for one product: [ { "productId": 1, "rating": 5, "comment": "Excellent build quality", "reviewer": "Amara" }, { "productId": 1, "rating": 3, "comment": "Battery drains quickly", "reviewer": "Devon" } ] Sales report: { "totalSales": 5400, "unitsSold": 18, "averagePrice": 300 } Using questions only, guide me toward designing TypeScript interfaces for this data. Do not write the interfaces for me. -
Work through the question arc. Answer each question in your own words before moving on. Expect Claude to probe topics such as:
- Which properties every record of a given kind shares.
- Whether any property could reasonably be absent, and whether it should be marked optional with
?. - Whether
idshould be anumber, astring, or a union type, and what each choice would cost you later. - What the return type of each simulated API function should be, given that the data arrives asynchronously.
-
Write
src/types.tsyourself. Define and export three interfaces:Product,Review, andSalesReport. Base every property name and type on the mock data above and on the conclusions you reached in the dialogue. -
Plan the function signatures. In a comment at the bottom of
types.ts, write the three signatures you intend to implement in Part 3. They should returnPromise<Product[]>,Promise<Review[]>, andPromise<SalesReport>respectively. -
Checkpoint 1. Paste your finished
types.tsinto the dialogue and send:Interrogate my interfaces with questions only. Do not rewrite them. Probe for weaknesses in my property types, my use of optional properties, and my planned Promise return types.Revise your interfaces yourself if the questioning exposes a weakness. Then record, under Checkpoint 1 in
REFLECTION.md, the two questions from this dialogue that most changed (or most strongly tested) your design.
Part 3: Build the Simulator and Promise Chain (60 minutes)
Now implement the dashboard exactly as in the original challenge, typed with the interfaces you designed in Part 2. This part is your own work from start to finish: the Socratic Tutor is available if you get stuck, but remember that it answers only with questions.
-
Create custom error classes in
src/errors.ts. Following the pattern from Lesson 5, define and export two classes:export class NetworkError extends Error { constructor(message: string) { super(message); this.name = "NetworkError"; } } export class DataError extends Error { constructor(message: string) { super(message); this.name = "DataError"; } }NetworkErrorrepresents network-related failures.DataErrorrepresents data-related failures, such as a response that is missing required fields. Rejecting withErrorinstances instead of plain strings preserves stack traces and lets your handlers narrow the error type withinstanceof, as covered in Lesson 5. -
Implement the API simulation functions in
src/apiSimulator.ts. Each returns a Promise that resolves with mock data after a delay, or rejects with one of your custom errors. Here isfetchProductCatalog()in full:import { Product } from "./types"; import { NetworkError } from "./errors"; export const fetchProductCatalog = (): Promise<Product[]> => { return new Promise((resolve, reject) => { setTimeout(() => { if (Math.random() < 0.8) { resolve([ { id: 1, name: "Laptop", price: 1200 }, { id: 2, name: "Headphones", price: 200 }, ]); } else { reject(new NetworkError("Failed to fetch product catalog")); } }, 1000); }); };Continue with the remaining two functions, written by you:
fetchProductReviews(productId: number): Promise<Review[]>: Resolves with an array of mock reviews for the given product after a 1.5-second delay. Randomly rejects with aNetworkErrorwhose message includes the product ID, for example`Failed to fetch reviews for product ID ${productId}`.fetchSalesReport(): Promise<SalesReport>: Resolves with a mock sales report after a 1-second delay. Randomly rejects with aNetworkErrorsuch as"Failed to fetch sales report". Add a second, separate random branch that rejects with aDataErrorsuch as"Sales report is missing required fields", so your handlers must distinguish the two error types.
-
Build the main application logic in
src/index.ts:- Use
fetchProductCatalog()to fetch the products and display them in the console. - For each product, fetch its reviews with
fetchProductReviews(product.id)and display them. You may chain these calls sequentially or run them together withPromise.all(); be prepared to defend your choice in Part 4. - After the products and reviews, retrieve the sales report with
fetchSalesReport()and display it. - Attach a
.catch()to each call so that one failure does not silence the others, and log a distinct message for each failure. Useinstanceofto report whether a failure was aNetworkErroror aDataError. - Attach a
.finally()that logs a summary message indicating that all API calls have been attempted.
The overall shape of one link in the chain looks like this; the complete flow is yours to design:
fetchProductCatalog() .then((products) => { // Display the products, then fetch reviews for each product. }) .catch((error) => { // Identify and report the failure without stopping the program. }) .finally(() => { console.log("Product catalog request attempted."); }); - Use
-
Compile and run your application:
npx tsc node dist/index.jsBecause the simulators fail at random, run the program several times until you have observed both the success path and each failure path. Confirm that a failed reviews call does not prevent the sales report from being fetched, and that the
.finally()summary always appears.
Part 4: Socratic Dialogue: From Promises to Async/Await (30 minutes)
With the promise chain working, you will now reason through the transition to async/await from Lesson 7, then perform one refactor by hand.
-
Open the transition dialogue. Send Claude these seed prompts, one at a time, and work through each question arc:
Ask me questions, one at a time, until I can explain what the await keyword does with a settled promise.Question me about where my .catch() handlers go when this promise chain becomes try/catch.During this dialogue, expect Claude to probe what
.then()returns, how rejections propagate through a chain, how.finally()maps to thefinallyblock, and when a sequence ofawaitexpressions should become a singlePromise.all(). -
Write your explanation. In
REFLECTION.md, under the final heading, write 150 to 250 words in your own words explaining the transition from Promises toasync/await. Cover: whatawaitdoes with a settled promise, where error handling moves when.catch()becomestry/catch, what happens to.finally(), and one situation wherePromise.all()remains the right tool. Do not paste any of Claude’s wording; the explanation must be yours. -
Refactor exactly one function by hand. In
src/index.ts, keep your promise-chain version as a named function (for example,displayDashboardWithPromises), and write a newasyncfunction (for example,displayDashboardAsync) that performs the same flow usingawaitwithtry/catchandfinally. Update the file so the async version runs. Do not refactor the simulator functions; they stay promise-based. No AI-written code is permitted: this refactor is yours alone. -
Checkpoint 2. Compile and run again to confirm the async version behaves like the original. Then paste your refactored function into the dialogue and send:
Question me about the edge cases in this refactor. Focus on what happens when the reviews call rejects, on when my finally block runs, and on how I narrow the error type inside the catch block.Note that in TypeScript the
catchparameter is not automatically typed, so narrowing withinstanceof NetworkErrororinstanceof DataErrorinside thecatchblock is the pattern to defend. Revise your refactor yourself if the questioning exposes a defect. -
Optional extension prompts. If time remains, continue the dialogue with the original challenge’s critical thinking questions:
- Why is it important to handle errors for each individual API call rather than only at the end of the chain?
- How do custom error classes improve debugging and error identification?
- When might a retry mechanism be more effective than an immediate failure response?
Part 5: Wrap-Up and Submission Preparation (15 minutes)
-
Generate your conversation evidence. On claude.ai, use the Share button on your Socratic conversation to create a public link, and paste it into
REFLECTION.md. If sharing is disabled in your workspace, or if you used Claude Code, copy representative transcript excerpts intoREFLECTION.mdinstead. The excerpts must show Claude asking guiding questions and must not show Claude writing code. -
Finish
REFLECTION.md. Confirm all three sections are complete: the conversation link or excerpts, the two Checkpoint 1 questions, and your 150 to 250 word explanation. -
Push to GitHub. Commit all project files, including
src/types.ts, your finalsrc/index.tscontaining both the promise-chain function and your async refactor, andREFLECTION.md. Push to a new GitHub repository:git add . git commit -m "Complete Lab 2 (AI): promises, interfaces, and Socratic dialogue" git branch -M main git remote add origin https://github.com/YOUR-USERNAME/promise-dashboard.git git push -u origin main -
Ensure the repository is publicly accessible, or that the appropriate permissions are granted for review.
Optional Challenge: Retry Mechanism
If you finish early, extend the application with a retry utility:
- Write a function
retryPromisethat accepts a promise-returning function, the number of retry attempts, and the delay between attempts.- Hint: Use
setTimeoutto delay the next attempt. - Hint: You will need to use recursion to implement this function. If you are not sure what recursion is, or do not quite remember, this is a good opportunity to practice your research skills, or to ask your Socratic Tutor to question you toward the idea.
- Hint: Use
- Use
retryPromiseto retry each failing API call up to three times before giving up.
The optional challenge is not required for a Complete grade.
Submission Guidelines
This lab is graded complete/incomplete. Submit the link to your GitHub repository on Canvas.
Your submission is marked Complete when all four of the following are true:
src/types.tscontains manually writtenProduct,Review, andSalesReportinterfaces that compile and are used to type the three API simulator functions.- The promise-chain application runs, with a
.catch()on each call, a.finally()summary, and customNetworkErrorandDataErrorclasses used in the rejections. - Exactly one function (the main display flow) is manually refactored to
async/awaitwithtry/catch, and the promise-chain version is preserved alongside it for comparison. REFLECTION.mdcontains your shared Claude conversation link (or pasted transcript excerpts) demonstrating that the AI asked guiding questions and wrote no code, the two Checkpoint 1 questions, and your 150 to 250 word learner-authored explanation of the Promises toasync/awaittransition.
Instructors will spot-check the shared conversation. Evidence that the AI wrote code that appears in your submission, or a missing conversation link with no transcript excerpts, will result in an Incomplete with an opportunity to revise and resubmit.