
Javascript Development
- 14 installs
- 7 repo stars
- Updated August 2, 2026
- practicalswan/agent-skills
javascript-development is a Claude Code skill for ai & agent building.
About
javascript-development is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- javascript-development
- AI & Agent Building
- AI-coding skill
Javascript Development by the numbers
- 14 all-time installs (skills.sh)
- Ranked #11,275 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/practicalswan/agent-skills --skill javascript-developmentAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 14 |
|---|---|
| repo stars | ★ 7 |
| Last updated | August 2, 2026 |
| Repository | practicalswan/agent-skills ↗ |
How do I helps with ai & agent building tasks.?
Helps with ai & agent building tasks.
Who is it for?
Best when you're working on ai & agent building and need structured help with javascript development.
Skip if: Teams with no ai & agent building needs, or anyone wanting a generic chat assistant without this specific workflow.
When should I use this skill?
When you need to helps with ai & agent building tasks., or when javascript-development is a claude code skill for ai & agent building.
What you get
Structured output aligned to javascript-development: javascript-development, AI & Agent Building.
Files
JavaScript Development
Optimized for ECMAScript 2024+, Node.js 22+, TypeScript 5.5+, and modern browser or server-first JavaScript runtimes.
Expert guidance for writing modern JavaScript code with ES2024+ features, async programming patterns, DOM manipulation, API integration, and best practices following official JavaScript resources at https://developer.mozilla.org/en-US/docs/Web/JavaScript.
- Leverage native parallel subagent dispatch and 200k+ context windows where available.
Anti-Patterns
- Copying outdated browser or framework patterns: Deprecated APIs and old workarounds add complexity immediately.
- Skipping abort, timeout, or response checks in async code: Network paths fail at the edges first, not on the happy path.
- Treating accessibility as a final polish pass: Markup and state shape are harder to fix after the component contract is set.
Verification Protocol
Before claiming "skill applied successfully":
1. Pass/fail: The Javascript Development implementation names the target runtime, framework version, and affected files. 2. Pass/fail: Build, lint, test, or equivalent local validation is run for the changed surface. 3. Pass/fail: Edge cases for errors, dependency drift, and environment differences are addressed or explicitly out of scope. 4. Pressure-test scenario: Apply the workflow to a change that passes happy-path tests but fails one boundary condition. 5. Success metric: Zero untested success claims; every implementation claim maps to a command or artifact.
Before and After Example
// Before
async function loadProfile() {
const response = await fetch('/api/profile');
return response.json();
}
// After
export async function loadProfile(signal) {
const response = await fetch('/api/profile', { signal });
if (!response.ok) {
throw new Error(`Profile request failed: ${response.status}`);
}
return response.json();
}Adds cancellation and explicit response validation so network failures do not masquerade as parsing bugs.
Activation Conditions
Use symptom -> action triggers: when one matches, apply this skill and verify with the protocol below.
Core JavaScript Development:
- Writing modern JavaScript with ES2024+ features
- Creating React components and hooks
- Working with DOM manipulation and events
- Implementing forms and user interactions
- Managing state in frontend applications
Asynchronous Programming:
- Using Promises and async/await patterns
- Fetching data from APIs
- Handling loading and error states
- Implementing retry mechanisms
- Working with concurrent operations
Data Handling:
- Manipulating arrays and objects
- Using modern array methods (map, filter, reduce, find)
- Working with JSON data
- LocalStorage and session management
- Data transformation and formatting
API Integration:
- Fetch API for HTTP requests
- Axios for advanced HTTP client features
- RESTful API design and consumption
- Authentication with JWT tokens
- CORS and error handling
---
Part 1: Modern JavaScript (ES2024+)
New Features & Syntax
// Logical Assignment Operators (ES2021)
const obj = { a: 1 };
obj.a ??= 10; // Only assign if obj.a is null/undefined
console.log(obj.a); // 1 (no change)
obj.b ??= 20; // Assign if missing
console.log(obj.b); // 20
// Numeric Separators (ES2021)
const billion = 1_000_000_000;
const bytes = 0xff_13_ff; // Hex
// String methods (ES2022)
const str = "Hello World";
console.log(str.replaceAll('l', 'L')); // "HeLLo WorLd"
console.log(str.at(-1)); // "d" (last character)
// Array methods (ES2023)
const array = [1, 2, 3, 4, 5];
console.log(array.toReversed()); // [5, 4, 3, 2, 1]
console.log(array.toSorted()); // [1, 2, 3, 4, 5]
console.log(array.with(2, 99)); // [1, 2, 99, 4, 5] (non-mutating)
// Hashbang (for scripts)
#!/usr/bin/env node
console.log("Executable script!");
// Object.groupBy (ES2024)
const people = [
{ name: "Alice", age: 25, role: "admin" },
{ name: "Bob", age: 30, role: "user" },
{ name: "Charlie", age: 25, role: "user" },
];
const groupedByAge = Object.groupBy(people, ({ age }) => age);
console.log(groupedByAge);
// { 25: [{name: "Alice", age: 25, role: "admin"}, ...], 30: [...] }
const groupedByRole = Map.groupBy(people, ({ role }) => role);
console.log(groupedByRole);
// Map { "admin" => [...], "user" => [...] }Template Literals & Tagged Templates
// Template literals with expressions
const firstName = "John";
const lastName = "Doe";
const greeting = `Hello, ${firstName} ${lastName}!`;
// Tagged template for custom formatting
function highlight(strings, ...values) {
return strings.reduce((result, str, i) => {
return result + str + (values[i] ? `<strong>${values[i]}</strong>` : '');
}, '');
}
const message = highlight`User ${firstName} is online.`;
// "User <strong>John</strong> is online."Destructuring & Spread
// Object destructuring
const user = { id: 1, name: "Alice", email: "alice@example.com", role: "admin" };
const { name, email, role: userRole } = user;
console.log(name, email, userRole); // "Alice", "alice@example.com", "admin"
// Array destructuring
const numbers = [1, 2, 3, 4, 5];
const [first, second, ...rest] = numbers;
console.log(first, second, rest); // 1, 2, [3, 4, 5]
// Destructuring in function parameters
function processRecipe({ title, difficulty, ingredients = [] }) {
return `${title} (${difficulty}) - ${ingredients.length} ingredients`;
}
const recipe = { title: "Pasta", difficulty: "Medium", ingredients: ["Pasta", "Sauce"] };
processRecipe(recipe); // "Pasta (Medium) - 2 ingredients"---
Part 2: Async Programming
Async/Await Patterns
// Basic async/await
async function fetchUser(userId) {
try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const user = await response.json();
return user;
} catch (error) {
console.error("Failed to fetch user:", error);
throw error;
}
}
// Parallel async operations with Promise.all
async function fetchRecipeData(recipeId) {
try {
const [recipe, reviews, ingredients] = await Promise.all([
fetch(`/api/recipes/${recipeId}`).then(r => r.json()),
fetch(`/api/recipes/${recipeId}/reviews`).then(r => r.json()),
fetch(`/api/recipes/${recipeId}/ingredients`).then(r => r.json()),
]);
return { recipe, reviews, ingredients };
} catch (error) {
console.error("Failed to fetch recipe data:", error);
throw error;
}
}
// Race with Promise.any (ES2021)
async function fetchFromMultipleEndpoints() {
const endpoints = [
'/api/v1/users',
'/api/v2/users',
'/api/v3/users',
];
try {
const response = await Promise.any(
endpoints.map(url => fetch(url).then(r => r.json()))
);
return response;
} catch (error) {
// All promises rejected
console.error("All endpoints failed:", error);
throw error;
}
}
// All Settled (ES2020)
async function fetchWithStatus() {
const requests = [
fetch('/api/users'),
fetch('/api/recipes'),
fetch('/api/stats'),
];
const results = await Promise.allSettled(requests);
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`Request ${index} successful`);
} else {
console.error(`Request ${index} failed:`, result.reason);
}
});
}Async Iterator (ES2018)
// Async generator function
async function* fetchPaginatedUsers(pageSize = 10) {
let page = 1;
let hasMore = true;
while (hasMore) {
const response = await fetch(`/api/users?page=${page}&limit=${pageSize}`);
const { users, totalPages } = await response.json();
yield* users;
page++;
hasMore = page <= totalPages;
// Add delay to avoid rate limiting
await new Promise(resolve => setTimeout(resolve, 500));
}
}
// Using async iterator
async function getAllUsers() {
const userIterator = fetchPaginatedUsers();
const allUsers = [];
for await (const user of userIterator) {
allUsers.push(user);
console.log(`Fetched: ${user.name}`);
}
return allUsers;
}Top-level Await (ES2022)
// In ES modules, you can use await at top level
const config = await fetch('/api/config').then(r => r.json());
console.log('App config loaded:', config);
// This is useful for modules that need to load data before export
export const recipes = await fetch('/api/recipes').then(r => r.json());
export const users = await fetch('/api/users').then(r => r.json());---
Part 3: API Integration
Fetch API Patterns
// Basic GET request
async function getRecipes(filters = {}) {
const queryParams = new URLSearchParams(filters);
const url = `/api/recipes?${queryParams}`;
const response = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
});
if (!response.ok) {
throw new Error(`Request failed: ${response.status}`);
}
return await response.json();
}
// POST request with JSON body
async function createRecipe(recipeData) {
const response = await fetch('/api/recipes', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify(recipeData),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.message || 'Failed to create recipe');
}
return await response.json();
}
// PUT request with authentication
async function updateRecipe(recipeId, recipeData, token) {
const response = await fetch(`/api/recipes/${recipeId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': `Bearer ${token}`,
},
body: JSON.stringify(recipeData),
});
if (!response.status === 200 && response.status !== 204) {
throw new Error(`Update failed: ${response.status}`);
}
return await response.json();
}
// DELETE request
async function deleteRecipe(recipeId, token) {
const response = await fetch(`/api/recipes/${recipeId}`, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${token}`,
},
});
if (!response.ok) {
throw new Error(`Delete failed: ${response.status}`);
}
return true;
}Axios Integration
import axios from 'axios';
// Create axios instance with default config
const api = axios.create({
baseURL: '/api',
timeout: 10000,
headers: {
'Content-Type': 'application/json',
},
});
// Request interceptor for adding authentication
api.interceptors.request.use(
(config) => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => Promise.reject(error)
);
// Response interceptor for error handling
api.interceptors.response.use(
(response) => response,
(error) => {
if (error.response?.status === 401) {
// Unauthorized - redirect to login
localStorage.removeItem('token');
window.location.href = '/login';
}
return Promise.reject(error);
}
);
// API methods
export const recipesApi = {
async getAll(filters = {}) {
const response = await api.get('/recipes', { params: filters });
return response.data;
},
async getById(recipeId) {
const response = await api.get(`/recipes/${recipeId}`);
return response.data;
},
async create(recipeData) {
const response = await api.post('/recipes', recipeData);
return response.data;
},
async update(recipeId, recipeData) {
const response = await api.put(`/recipes/${recipeId}`, recipeData);
return response.data;
},
async delete(recipeId) {
const response = await api.delete(`/recipes/${recipeId}`);
return response.data;
},
};Error Handling & Retry
// Retry utility with exponential backoff
async function fetchWithRetry(url, options = {}, maxRetries = 3) {
let lastError;
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return response.json();
} catch (error) {
lastError = error;
console.log(`Attempt ${attempt + 1} failed, retrying...`);
// Exponential backoff: 1s, 2s, 4s
const delay = Math.pow(2, attempt) * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
}
}
throw new Error(`Failed after ${maxRetries} attempts: ${lastError.message}`);
}
// Usage with error handling
async function getRecipeWithRetry(recipeId) {
try {
const recipe = await fetchWithRetry(`/api/recipes/${recipeId}`);
console.log('Recipe loaded:', recipe);
return recipe;
} catch (error) {
console.error('Failed to load recipe:', error);
// Show user-friendly error message
return { error: true, message: 'Failed to load recipe. Please try again.' };
}
}---
Part 4: DOM Manipulation & Events
Element Selection & Manipulation
// Modern element selection
const button = document.querySelector('#submit-btn');
const items = document.querySelectorAll('.list-item');
const container = document.getElementById('container');
const firstChild = document.querySelector('.item:first-child');
// Using closest for event delegation
document.addEventListener('click', (event) => {
const button = event.target.closest('.action-button');
if (button) {
console.log('Button clicked:', button.dataset.id);
}
});
// Element creation and manipulation
function addRecipeToList(recipe) {
const li = document.createElement('li');
li.className = 'recipe-item';
li.dataset.recipeId = recipe.id;
li.innerHTML = `
<h3>${recipe.title}</h3>
<p>${recipe.description}</p>
<button class="delete-btn">Delete</button>
`;
// Add event listener to delete button
const deleteBtn = li.querySelector('.delete-btn');
deleteBtn.addEventListener('click', () => deleteRecipe(recipe.id));
// Append to container
document.getElementById('recipe-list').appendChild(li);
}
// Element removal
function removeRecipeElement(recipeId) {
const element = document.querySelector(`[data-recipe-id="${recipeId}"]`);
if (element) {
element.remove();
}
}Event Handling
// Form submission with validation
const form = document.getElementById('recipe-form');
form.addEventListener('submit', async (event) => {
event.preventDefault(); // Prevent default form submission
const formData = new FormData(form);
const recipeData = {
title: formData.get('title'),
description: formData.get('description'),
category: formData.get('category'),
difficulty: formData.get('difficulty'),
};
// Validation
if (!recipeData.title || recipeData.title.length < 3) {
showError('Title is required and must be at least 3 characters');
return;
}
try {
// Submit to API
const response = await fetch('/api/recipes', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(recipeData),
});
if (response.ok) {
showSuccess('Recipe created successfully!');
form.reset();
}
} catch (error) {
showError('Failed to create recipe: ' + error.message);
}
});
// Debounce for search input
let searchTimeout;
const searchInput = document.getElementById('search');
searchInput.addEventListener('input', (event) => {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
const query = event.target.value;
if (query.length >= 2) {
performSearch(query);
}
}, 300); // Wait 300ms after user stops typing
});
// Intersection Observer for infinite scroll
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
loadMoreRecipes();
}
});
}, {
root: null,
threshold: 0.1,
});
// Observe sentinel element
const sentinel = document.getElementById('load-more-sentinel');
observer.observe(sentinel);---
Part 5: Data Structures & Algorithms
Array Methods
// map - Transform array
const recipes = [
{ title: 'Pasta', difficulty: 'Medium' },
{ title: 'Salad', difficulty: 'Easy' },
];
const titles = recipes.map(recipe => recipe.title);
// ['Pasta', 'Salad']
// filter - Select elements
const easyRecipes = recipes.filter(recipe => recipe.difficulty === 'Easy');
// [{ title: 'Salad', difficulty: 'Easy' }]
// reduce - Aggregate array
const wordCount = recipes.reduce((count, recipe) => {
return count + recipe.title.split(' ').length;
}, 0);
// find - Find first matching element
const pasta = recipes.find(recipe => recipe.title.includes('Pasta'));
// some - Check if any matches
const hasMediumDifficulty = recipes.some(recipe => recipe.difficulty === 'Medium'); // true
// every - Check if all match
const allHaveTitles = recipes.every(recipe => recipe.title.length > 0); // true
// sort - Sort array
const sortedRecipes = [...recipes].sort((a, b) =>
a.title.localeCompare(b.title)
);
// flatMap - Map and flatten
const nested = [[1, 2], [3, 4]];
const flattened = nested.flatMap(arr => arr); // [1, 2, 3, 4]Object Methods
// Object.keys, Object.values, Object.entries
const user = {
id: 1,
name: 'Alice',
email: 'alice@example.com',
role: 'admin',
};
const keys = Object.keys(user); // ['id', 'name', 'email', 'role']
const values = Object.values(user); // [1, 'Alice', 'alice@example.com', 'admin']
const entries = Object.entries(user);
// [['id', 1], ['name', 'Alice'], ['email', 'alice@example.com'], ['role', 'admin']]
// Object.fromEntries - Convert back to object
const filtered = Object.fromEntries(
Object.entries(user).filter(([key]) => key !== 'role')
);
// { id: 1, name: 'Alice', email: 'alice@example.com' }
// Object.freeze - Make immutable
const config = Object.freeze({ apiUrl: '/api/v1' });
config.apiUrl = '/api/v2'; // Error in strict modeSet and Map
// Set - Unique values
const tags = new Set(['Easy', 'Medium', 'Medium', 'Hard']);
console.log(tags.size); // 3
tags.add('Easy');
console.log(tags.has('Medium')); // true
tags.delete('Hard');
const uniqueTags = Array.from(tags); // ['Easy', 'Medium']
// Map - Key-value pairs with any type
const userRoles = new Map();
userRoles.set(1, 'admin');
userRoles.set(2, 'user');
userRoles.set('alice', 'editor');
console.log(userRoles.get(1)); // 'admin'
console.log(userRoles.has('alice')); // true
const roles = Array.from(userRoles.entries());
// [[1, 'admin'], [2, 'user'], ['alice', 'editor']]---
Part 6: Date & Time
Date Operations
// Creating dates
const now = new Date();
const specificDate = new Date('2024-02-01');
const fromTimestamp = new Date(17068896000000);
// Date formatting
function formatDate(date) {
const options = {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
};
return new Intl.DateTimeFormat('en-US', options).format(date);
}
// "February 1, 2024 at 12:00 PM"
// Relative time (e.g., "2 hours ago")
function getRelativeTime(date) {
const now = new Date();
const diff = now - date;
const seconds = Math.floor(diff / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
const days = Math.floor(hours / 24);
if (seconds < 60) return 'just now';
if (minutes < 60) return `${minutes} minute${minutes > 1 ? 's' : ''} ago`;
if (hours < 24) return `${hours} hour${hours > 1 ? 's' : ''} ago`;
return `${days} day${days > 1 ? 's' : ''} ago`;
}
// Date manipulation
function addDays(date, days) {
const result = new Date(date);
result.setDate(result.getDate() + days);
return result;
}
function isSameDay(date1, date2) {
return date1.toDateString() === date2.toDateString();
}---
Part 7: LocalStorage & State Management
LocalStorage Wrapper
class StorageManager {
static set(key, value) {
try {
localStorage.setItem(key, JSON.stringify(value));
} catch (error) {
console.error('Failed to save to localStorage:', error);
}
}
static get(key, defaultValue = null) {
try {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : defaultValue;
} catch (error) {
console.error('Failed to read from localStorage:', error);
return defaultValue;
}
}
static remove(key) {
try {
localStorage.removeItem(key);
} catch (error) {
console.error('Failed to remove from localStorage:', error);
}
}
static clear() {
try {
localStorage.clear();
} catch (error) {
console.error('Failed to clear localStorage:', error);
}
}
static has(key) {
return localStorage.getItem(key) !== null;
}
}State Management (Simple)
class StateManager {
constructor(initialState = {}) {
this.state = initialState;
this.listeners = [];
}
setState(newState) {
this.state = { ...this.state, ...newState };
this.notify();
}
subscribe(listener) {
this.listeners.push(listener);
listener(this.state);
}
notify() {
this.listeners.forEach(listener => listener(this.state));
}
}
// Usage
const stateManager = new StateManager({
user: null,
recipes: [],
loading: false,
});
stateManager.subscribe((state) => {
console.log('State updated:', state);
// Update UI
});
stateManager.setState({ recipes: [...] });---
Part 8: Utility Functions
Common Utilities
// Debounce
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// Throttle
function throttle(func, limit) {
let inThrottle;
return function(...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
// Random ID
function generateId() {
return Math.random().toString(36).substr(2, 9);
}
// Slugify text
function slugify(text) {
return text
.toString()
.toLowerCase()
.trim()
.replace(/\s+/g, '-')
.replace(/[^\w\-]+/g, '');
}
// Format number with commas
function formatNumber(num) {
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
// Truncate text
function truncate(text, maxLength) {
return text.length > maxLength
? text.substring(0, maxLength - 3) + '...'
: text;
}---
JavaScript Development Best Practices
Code Quality
- [ ] Use
constandletinstead ofvar - [ ] Use strict mode (
'use strict') - [ ] Add JSDoc comments for functions
- [ ] Use modern ES2024+ features
- [ ] Avoid global variables
- [ ] Use meaningful variable and function names
Asynchronous Code
- [ ] Prefer async/await over .then() chains
- [ ] Handle errors with try-catch
- [ ] Use Promise.all for parallel operations
- [ ] Implement retry logic for network requests
- [ ] Provide loading states for async operations
DOM & Events
- [ ] Use event delegation for dynamic content
- [ ] Clean up event listeners to prevent memory leaks
- [ ] Use dataset for custom data attributes
- [ ] Validate form input before submission
- [ ] Use debouncing/throttling for rapid events
Performance
- [ ] Minimize DOM manipulation
- [ ] Use document fragments for bulk inserts
- [ ] Implement lazy loading for images/lists
- [ ] Cache expensive computations
- [ ] Use requestAnimationFrame for animations
Security
- [ ] Sanitize user input to prevent XSS
- [ ] Use HTTPS for API calls
- [ ] Store tokens securely (HttpOnly cookies)
- [ ] Validate data from localStorage
- [ ] Implement CSRF protection
---
Modern Component and Testing Examples
Server Components
export default async function ProfileCard({ userId }) {
const user = await getUser(userId);
return <section>{user.name}</section>;
}Error Boundaries
import { ErrorBoundary } from 'react-error-boundary';
<ErrorBoundary fallbackRender={() => <p>Something went wrong.</p>}>
<ProfileDashboard />
</ErrorBoundary>Accessibility Testing Tools
import { axe } from 'jest-axe';
test('search form has no obvious accessibility violations', async () => {
const { container } = render(<SearchForm />);
expect(await axe(container)).toHaveNoViolations();
});Common Pitfalls
- Copying outdated browser or framework patterns: Deprecated APIs and unnecessary workarounds add complexity immediately.
- Skipping response and abort handling: Network code fails in edge cases first, so the happy path alone is misleading.
- Treating accessibility as post-processing: Component structure is harder to fix later if semantics were not built in from the start.
References & Resources
Official Documentation
- MDN Web Docs — Complete JavaScript reference
- ECMAScript 2024 Spec — Latest language specification
- Fetch API — HTTP requests with Fetch
- Web Storage API — LocalStorage and SessionStorage
Libraries & Tools
- Axios Documentation — Popular HTTP client library
- Vite Documentation — Build tool for modern web apps
- ESLint — Code linting for JavaScript
- Prettier — Code formatting tool
Learning Resources
- JavaScript.info — Modern JavaScript tutorial
- JavaScript 30 — 30-day JavaScript challenge
- You Don't Know JS — Deep dive into JavaScript
---
<!-- PORTABILITY:START -->
Cross-Client Portability
This skill is written to stay usable across GitHub Copilot, Claude Code, Codex, and Gemini CLI.
- GitHub Copilot: keep the folder in a Copilot-visible skill or plugin path, or wrap the workflow as project instructions if the host does not support portable skill folders directly.
- Claude Code: keep the folder in a local skills directory or a compatible plugin or marketplace source.
- Codex: install or sync the folder into
$CODEX_HOME/skills/<skill-name>and restart Codex after major changes. - Gemini CLI: this repository generates a project command named
/skills:javascript-developmentfrom this skill. Rebuild commands withpython scripts/export-gemini-skill.py javascript-developmentand then run/commands reloadinside Gemini CLI.
<!-- PORTABILITY:END -->
<!-- MCP:START -->
MCP Availability And Fallback
Preferred MCP Server: None required
- Fallback prompt: "Use the JavaScript Development skill without MCP. Rely on the local
SKILL.md, bundled references or scripts, and manual verification. Show the exact commands, evidence, and final checks you used before concluding." - If the current host does not expose a matching server, use the bundled references, scripts, native toolchain, and manual workflow already described in this skill.
- Treat direct local verification, rendered output, logs, tests, or screenshots as the fallback evidence path before completion.
<!-- MCP:END -->
Related Skills
- react-development: Use it when the workflow also needs React component architecture and client or server boundaries.
- nextjs-development: Use it when the workflow also needs Next.js App Router and server-first React patterns.
- vite-development: Use it when the workflow also needs Vite build and development-server configuration.
- web-testing: Use it when the workflow also needs browser and end-to-end testing evidence.
Changelog
[2026-04-25] - Version 1.2 Verification Protocol Refresh
Added
- Added a
Verification Protocolsection with skill-specific pass/fail checks, one pressure-test scenario, and a measurable success metric. - Added guidance to leverage native parallel subagent dispatch and 200k+ context windows where available.
Changed
- Updated
SKILL.mdfrontmatter toversion: "1.2"andlast_updated: 2026-04-25. - Reframed activation guidance toward symptom -> action triggers and standardized two-stage review wording where applicable.
[2026-04-24] - Version 1.1 Refresh
Changed
- Updated the SKILL frontmatter version to
1.1for the 2026-04-24 catalog refresh.
[2026-04-24] - Skill Refresh
Changed
- Standardized the SKILL frontmatter with version metadata, last-updated date, tags, and a concise catalog description.
- Reformatted the portability and MCP guidance with a preferred server line, a copy-paste fallback prompt, and consistent bullet lists.
- Added a catalog-standard Anti-Patterns section and refreshed the Related Skills links at the end of the skill.
- Added current-version targeting, a before-and-after example, Common Pitfalls, and modern examples for Server Components, Error Boundaries, and accessibility testing tools.
[2026-04-24] - Catalog Audit Cleanup
Fixed
- Replaced stale
nestjsrelated-skill references with existing maintained skill links.
All notable changes to this skill will be documented in this file.
[2026-04-04] - Cross-Client Portability Refresh
Changed
- Added a standard portability note covering GitHub Copilot, Claude Code, Codex, and Gemini CLI.
- Clarified that the core workflow does not require a dedicated MCP server and can run with local tools alone.
Tested
- Validated
SKILL.mdfrontmatter, portability sections, and Gemini export readiness withpython scripts/validate-skills.py.
[2026-03-09] - Workspace Modernization
Added
- Added a 2026-03-09 maintenance entry after reviewing the skill; the earlier TypeScript activation fix remained the only content change needed.
[2026-03-01] — Activation Fix
Fixed
- Added "TypeScript" keyword to description — prompts about TypeScript without React context now correctly activate this skill instead of react-development
- Changed "JavaScript ES2024+" to "JavaScript/TypeScript ES2024+" and "vanilla JS code" to "vanilla JS/TS code"
[2026-02-28] — Description Rewrite & Cross-References
Changed
- Rewrote skill description to ~200 characters with clear, specific activation keywords
- Improved keyword specificity to reduce overlap with related skills
Added
## Related Skillscross-reference table with 2-4 related skills and "Use When" guidance
JavaScript API Integration Patterns
Modern patterns for API integration in JavaScript using Fetch API and Axios, with error handling, retry logic, and authentication.
Fetch API Patterns
Basic GET Request
/**
* Fetch all published recipes from the API
* @param {Object} filters - Query parameters for filtering
* @param {string} filters.category - Filter by recipe category
* @param {string} filters.difficulty - Filter by difficulty level
* @param {number} filters.limit - Maximum number of results
* @param {number} filters.offset - Offset for pagination
* @returns {Promise<Array>} Array of recipe objects
*/
async function getRecipes(filters = {}) {
// Build query parameters
const queryParams = new URLSearchParams();
if (filters.category) queryParams.append('category', filters.category);
if (filters.difficulty) queryParams.append('difficulty', filters.difficulty);
if (filters.limit) queryParams.append('limit', filters.limit);
if (filters.offset) queryParams.append('offset', filters.offset);
if (filters.search) queryParams.append('search', filters.search);
const queryString = queryParams.toString();
const url = `/api/recipes${queryString ? `?${queryString}` : ''}`;
try {
const response = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.message || `HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
if (!data.success) {
throw new Error(data.error || 'Request failed');
}
return data.data;
} catch (error) {
console.error('Failed to fetch recipes:', error);
throw error;
}
}POST Request
/**
* Create a new recipe
* @param {Object} recipeData - Recipe data to create
* @param {string} recipeData.title - Recipe title (required)
* @param {string} recipeData.description - Recipe description
* @param {string} recipeData.category - Recipe category
* @param {string} recipeData.difficulty - Recipe difficulty
* @param {Array} recipeData.ingredients - Array of ingredients
* @param {Array} recipeData.instructions - Array of instructions
* @param {string} token - Authentication token
* @returns {Promise<Object>} Created recipe object
*/
async function createRecipe(recipeData, token) {
// Validate required fields
if (!recipeData.title || recipeData.title.trim().length < 3) {
throw new Error('Title is required and must be at least 3 characters');
}
try {
const response = await fetch('/api/recipes', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': `Bearer ${token}`,
},
body: JSON.stringify({
title: recipeData.title.trim(),
description: recipeData.description?.trim() || '',
category: recipeData.category || 'Uncategorized',
difficulty: recipeData.difficulty || 'Medium',
prep_time: parseInt(recipeData.prepTime) || 0,
cook_time: parseInt(recipeData.cookTime) || 0,
servings: parseInt(recipeData.servings) || 1,
ingredients: recipeData.ingredients || [],
instructions: recipeData.instructions || [],
}),
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.error || `Failed to create recipe: ${response.status}`);
}
return data.data;
} catch (error) {
console.error('Error creating recipe:', error);
throw error;
}
}PUT/PATCH Request
/**
* Update an existing recipe
* @param {number} recipeId - ID of the recipe to update
* @param {Object} updates - Fields to update
* @param {string} token - Authentication token
* @returns {Promise<Object>} Updated recipe object
*/
async function updateRecipe(recipeId, updates, token) {
try {
const response = await fetch(`/api/recipes/${recipeId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': `Bearer ${token}`,
},
body: JSON.stringify(updates),
});
if (response.status === 404) {
throw new Error('Recipe not found');
}
if (response.status === 403) {
throw new Error('You do not have permission to update this recipe');
}
const data = await response.json();
if (!response.ok) {
throw new Error(data.error || 'Failed to update recipe');
}
return data.data;
} catch (error) {
console.error('Error updating recipe:', error);
throw error;
}
}DELETE Request
/**
* Delete a recipe
* @param {number} recipeId - ID of the recipe to delete
* @param {string} token - Authentication token
* @returns {Promise<boolean>} True if successful
*/
async function deleteRecipe(recipeId, token) {
try {
const response = await fetch(`/api/recipes/${recipeId}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
},
});
if (response.status === 404) {
throw new Error('Recipe not found');
}
if (response.status === 403) {
throw new Error('You do not have permission to delete this recipe');
}
if (!response.ok) {
const data = await response.json().catch(() => ({}));
throw new Error(data.error || 'Failed to delete recipe');
}
return true;
} catch (error) {
console.error('Error deleting recipe:', error);
throw error;
}
}Axios Integration
Creating an Axios Instance
import axios from 'axios';
// Create base axios instance with configuration
const apiClient = axios.create({
baseURL: import.meta.env.VITE_API_URL || 'http://localhost:8080/api',
timeout: 10000, // 10 second timeout
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
});
// Request interceptor - Add authentication
apiClient.interceptors.request.use(
(config) => {
// Get token from localStorage
const token = localStorage.getItem('auth_token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
// Add request timestamp
config.metadata = { startTime: new Date() };
return config;
},
(error) => {
// Handle request error
console.error('Request interceptor error:', error);
return Promise.reject(error);
}
);
// Response interceptor - Handle errors and logging
apiClient.interceptors.response.use(
(response) => {
const { config } = response;
// Calculate request duration
const duration = new Date() - config.metadata.startTime;
console.log(`API ${config.method?.toUpperCase()} ${config.url} - ${response.status} (${duration}ms)`);
return response;
},
(error) => {
// Handle network errors
if (!error.response) {
console.error('Network error:', error.message);
return Promise.reject(new Error('Network error. Please check your connection.'));
}
const { response } = error;
// Handle 401 Unauthorized - Redirect to login
if (response?.status === 401) {
console.log('Unauthorized - Logging out...');
localStorage.removeItem('auth_token');
localStorage.removeItem('user_data');
// Only redirect if not already on login page
if (window.location.pathname !== '/login') {
window.location.href = '/login';
}
}
// Handle 403 Forbidden
if (response?.status === 403) {
console.error('Access forbidden:', response.data);
return Promise.reject(new Error('You do not have permission to access this resource.'));
}
// Handle 404 Not Found
if (response?.status === 404) {
return Promise.reject(new Error('Resource not found.'));
}
// Handle 500 Server Error
if (response?.status >= 500) {
return Promise.reject(new Error('Server error. Please try again later.'));
}
// Return other errors with message from server
const errorMessage = response?.data?.error || response?.data?.message || error.message;
return Promise.reject(new Error(errorMessage));
}
);
export default apiClient;API Service Methods
/**
* Recipe API Service
* Provides methods for all recipe-related API calls
*/
export const recipeApi = {
/**
* Get all published recipes with optional filters
* @param {Object} filters - Query parameters
* @returns {Promise<Object>} Response with data and pagination info
*/
getAll(filters = {}) {
const params = {};
if (filters.category) params.category = filters.category;
if (filters.difficulty) params.difficulty = filters.difficulty;
if (filters.search) params.search = filters.search;
if (filters.limit) params.limit = filters.limit;
if (filters.offset) params.offset = filters.offset;
if (filters.sort) params.sort = filters.sort;
return apiClient.get('/recipes', { params });
},
/**
* Get a specific recipe by ID
* @param {number} recipeId - Recipe ID
* @returns {Promise<Object>} Recipe object with full details
*/
getById(recipeId) {
return apiClient.get(`/recipes/${recipeId}`);
},
/**
* Create a new recipe
* @param {Object} recipeData - Recipe data
* @returns {Promise<Object>} Created recipe
*/
create(recipeData) {
return apiClient.post('/recipes', recipeData);
},
/**
* Update a recipe
* @param {number} recipeId - Recipe ID
* @param {Object} updates - Fields to update
* @returns {Promise<Object>} Updated recipe
*/
update(recipeId, updates) {
return apiClient.put(`/recipes/${recipeId}`, updates);
},
/**
* Delete a recipe
* @param {number} recipeId - Recipe ID
* @returns {Promise<Object>} Deletion confirmation
*/
delete(recipeId) {
return apiClient.delete(`/recipes/${recipeId}`);
},
/**
* Like/unlike a recipe
* @param {number} recipeId - Recipe ID
* @returns {Promise<Object>} Updated like status
*/
toggleLike(recipeId) {
return apiClient.post(`/recipes/${recipeId}/like`);
},
/**
* Favorite/unfavorite a recipe
* @param {number} recipeId - Recipe ID
* @returns {Promise<Object>} Updated favorite status
*/
toggleFavorite(recipeId) {
return apiClient.post(`/recipes/${recipeId}/favorite`);
},
/**
* Record a view for a recipe
* @param {number} recipeId - Recipe ID
* @returns {Promise<Object>} View record confirmation
*/
recordView(recipeId) {
return apiClient.post(`/recipes/${recipeId}/view`);
},
};
/**
* Authentication API Service
*/
export const authApi = {
/**
* Register a new user
* @param {Object} userData - User registration data
* @returns {Promise<Object>} User data and token
*/
register(userData) {
return apiClient.post('/auth/register', userData);
},
/**
* Login with email and password
* @param {Object} credentials - Login credentials
* @returns {Promise<Object>} User data and token
*/
login(credentials) {
return apiClient.post('/auth/login', credentials);
},
/**
* Logout current user
* @returns {Promise<Object>} Logout confirmation
*/
logout() {
return apiClient.post('/auth/logout');
},
/**
* Get current authenticated user
* @returns {Promise<Object>} Current user data
*/
getCurrentUser() {
return apiClient.get('/auth/me');
},
};
/**
* User API Service
*/
export const userApi = {
/**
* Get all users (admin only)
* @param {Object} filters - Query parameters
* @returns {Promise<Object>} Users list
*/
getAll(filters = {}) {
return apiClient.get('/users', { params: filters });
},
/**
* Get user by ID
* @param {number} userId - User ID
* @returns {Promise<Object>} User data
*/
getById(userId) {
return apiClient.get(`/users/${userId}`);
},
/**
* Update user profile
* @param {number} userId - User ID
* @param {Object} updates - Fields to update
* @returns {Promise<Object>} Updated user data
*/
update(userId, updates) {
return apiClient.put(`/users/${userId}`, updates);
},
/**
* Delete user (admin only)
* @param {number} userId - User ID
* @returns {Promise<Object>} Deletion confirmation
*/
delete(userId) {
return apiClient.delete(`/users/${userId}`);
},
/**
* Update user status (admin only)
* @param {number} userId - User ID
* @param {string} status - New status (active, inactive, suspended)
* @returns {Promise<Object>} Updated user data
*/
updateStatus(userId, status) {
return apiClient.put(`/users/${userId}/status`, { status });
},
};
/**
* Review API Service
*/
export const reviewApi = {
/**
* Get reviews for a recipe
* @param {number} recipeId - Recipe ID
* @returns {Promise<Object>} Reviews array
*/
getByRecipe(recipeId) {
return apiClient.get(`/recipes/${recipeId}/reviews`);
},
/**
* Create a review for a recipe
* @param {number} recipeId - Recipe ID
* @param {Object} reviewData - Review data
* @returns {Promise<Object>} Created review
*/
create(recipeId, reviewData) {
return apiClient.post(`/recipes/${recipeId}/reviews`, reviewData);
},
/**
* Update a review
* @param {number} reviewId - Review ID
* @param {Object} updates - Fields to update
* @returns {Promise<Object>} Updated review
*/
update(reviewId, updates) {
return apiClient.put(`/reviews/${reviewId}`, updates);
},
/**
* Delete a review
* @param {number} reviewId - Review ID
* @returns {Promise<Object>} Deletion confirmation
*/
delete(reviewId) {
return apiClient.delete(`/reviews/${reviewId}`);
},
};Error Handling Patterns
Centralized Error Handler
/**
* API Error Handler - Centralized error handling for all API calls
* @param {Error} error - The error object
* @param {Object} options - Additional options
* @returns {Object} Error response object
*/
export function handleApiError(error, options = {}) {
const {
showToast = true,
logError = true,
defaultMessage = 'An error occurred. Please try again.',
} = options;
// Log error to console
if (logError) {
console.error('API Error:', error);
}
// Determine error message
let errorMessage = defaultMessage;
let errorType = 'error';
if (error.isAxiosError) {
// Axios-specific errors
if (!error.response) {
errorMessage = 'Network error. Please check your connection.';
errorType = 'network';
} else {
const { status, data } = error.response;
switch (status) {
case 400:
errorMessage = data.error || 'Invalid request. Please check your input.';
break;
case 401:
errorMessage = 'Session expired. Please login again.';
errorType = 'auth';
break;
case 403:
errorMessage = 'Access denied.';
break;
case 404:
errorMessage = 'Resource not found.';
break;
case 422:
errorMessage = data.error || 'Validation error.';
break;
case 429:
errorMessage = 'Too many requests. Please wait.';
errorType = 'rate-limit';
break;
case 500:
case 502:
case 503:
errorMessage = 'Server error. Please try again later.';
errorType = 'server';
break;
default:
errorMessage = data.error || defaultMessage;
}
}
} else if (error.message) {
errorMessage = error.message;
}
// Show toast notification
if (showToast) {
// Assuming a toast notification library
showToastNotification(errorMessage, errorType);
}
return {
message: errorMessage,
type: errorType,
originalError: error,
};
}Wrapper for API Calls with Error Handling
/**
* Safe API wrapper with automatic error handling
* @param {Function} apiCall - The API function to call
* @param {Object} options - Options for error handling
* @returns {Promise<Object>} API response or error object
*/
export async function safeApiCall(apiCall, options = {}) {
const {
showLoading = true,
errorMessage = 'Request failed',
onSuccess = null,
onError = null,
} = options;
// Show loading state (assuming a global loading state)
let loadingId;
if (showLoading) {
loadingId = showGlobalLoading();
}
try {
const response = await apiCall();
if (onSuccess) {
onSuccess(response.data);
}
return {
success: true,
data: response.data,
};
} catch (error) {
const handledError = handleApiError(error, {
showToast: true,
defaultMessage: errorMessage,
});
if (onError) {
onError(handledError);
}
return {
success: false,
error: handledError,
};
} finally {
if (showLoading && loadingId) {
hideGlobalLoading(loadingId);
}
}
}Using the Safe API Wrapper
// Example: Loading recipes with error handling
async function loadRecipes(filters) {
const result = await safeApiCall(
() => recipeApi.getAll(filters),
{
errorMessage: 'Failed to load recipes',
onSuccess: (data) => {
console.log('Recipes loaded:', data.length);
},
}
);
if (result.success) {
setRecipes(result.data);
} else {
// Error already handled by safeApiCall
console.error('Error:', result.error);
}
}
// Example: Creating a recipe with validation
async function handleCreateRecipe(recipeData) {
const result = await safeApiCall(
() => recipeApi.create(recipeData),
{
errorMessage: 'Failed to create recipe',
onSuccess: (data) => {
console.log('Recipe created:', data);
navigate(`/recipes/${data.id}`);
},
onError: (error) => {
console.error('Form submission error:', error);
if (error.type === 'validation') {
setFormErrors(error.originalError.response.data.errors);
}
},
}
);
return result.success;
}Retry Logic
Exponential Backoff Retry
/**
* Retry a failed request with exponential backoff
* @param {Function} requestFn - Function to call
* @param {Object} options - Retry options
* @returns {Promise<Object>} Response data
*/
export async function retryWithBackoff(requestFn, options = {}) {
const {
maxRetries = 3,
initialDelay = 1000,
backoffMultiplier = 2,
retryIf = (error) => {
// Retry on network errors and 5xx status codes
if (!error.response) return true;
if (error.response.status >= 500) return true;
return false;
},
} = options;
let lastError;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
const response = await requestFn();
return response.data; // Return data on success
} catch (error) {
lastError = error;
// Don't retry if error doesn't meet retry condition
if (!retryIf?.(error)) {
throw error;
}
// Don't retry on last attempt
if (attempt >= maxRetries) {
throw error;
}
// Calculate delay with exponential backoff
const delay = initialDelay * Math.pow(backoffMultiplier, attempt);
console.log(`Attempt ${attempt + 1} failed, retrying in ${delay}ms...`);
// Wait before retrying
await new Promise(resolve => setTimeout(resolve, delay));
}
}
throw lastError;
}Using Retry with API Calls
// Example: Retry recipe fetching on failure
async function fetchRecipesWithRetry(filters) {
try {
const data = await retryWithBackoff(
() => recipeApi.getAll(filters),
{
maxRetries: 3,
initialDelay: 1000, // Start with 1 second
backoffMultiplier: 2, // 1s, 2s, 4s
}
);
return data;
} catch (error) {
handleApiError(error, {
errorMessage: 'Failed to load recipes after multiple attempts',
});
throw error;
}
}Request Cancellation
Using AbortController
/**
* Fetch function with cancellation support
* @param {Function} apiCall - API function to call
* @returns {Object} Object with promise and cancel function
*/
export function cancellableRequest(apiCall) {
const abortController = new AbortController();
const promise = apiCall({ signal: abortController.signal });
return {
promise,
cancel: () => abortController.abort(),
};
}
// Example: Cancelable search
function useRecipeSearch() {
const [results, setResults] = useState([]);
const [searching, setSearching] = useState(false);
const abortControllerRef = useRef(null);
const searchRecipes = useCallback(async (query) => {
// Cancel previous request
if (abortControllerRef.current) {
abortControllerRef.current.abort();
}
// Create new abort controller for this request
abortControllerRef.current = new AbortController();
setSearching(true);
try {
const data = await recipeApi.getAll({ search: query }, {
signal: abortControllerRef.current.signal,
});
setResults(data);
} catch (error) {
if (error.name !== 'AbortError') {
console.error('Search error:', error);
handleApiError(error);
}
} finally {
setSearching(false);
}
}, []);
// Cleanup on unmount
useEffect(() => {
return () => {
if (abortControllerRef.current) {
abortControllerRef.current.abort();
}
};
}, []);
return { results, searching, searchRecipes };
}Summary of Patterns
Fetch API
- ✅ Simple with just a function call
- ✅ No additional dependencies
- ✅ Built into all modern browsers
- ❌ No automatic request/response interception
- ❌ No automatic JSON parse for errors
Axios
- ✅ Powerful with interceptors
- ✅ Automatic JSON parsing
- ✅ Request cancellation via cancel tokens
- ✅ Better error handling
- ✅ Automatic XSRF protection
- ❌ Additional bundle size (~13KB)
When to Use Each
- Use Fetch for simple projects or when minimizing bundle size
- Use Axios for complex projects with authentication, error handling, and retry logic needs
MIT License
Copyright (c) 2026 Sithu Win San
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.JavaScript async/fetch Reference (from javascript.info)
Excerpted from the Modern JavaScript Tutorial (javascript.info) https://javascript-tutorial/en.javascript.info.
Fetch error handling with async/await
async function loadJson(url) {
let response = await fetch(url);
if (response.status == 200) {
let json = await response.json();
return json;
}
throw new Error(response.status);
}Fetch error handling with try/catch
async function f() {
try {
let response = await fetch('http://no-such-url');
} catch(err) {
alert(err); // TypeError: failed to fetch
}
}Promise chain error handling
fetch('https://no-such-server.blabla') // rejects
.then(response => response.json())
.catch(err => alert(err)) // TypeError: failed to fetch (the text may vary)Multiple async operations in one try/catch
async function f() {
try {
let response = await fetch('/no-user-here');
let user = await response.json();
} catch(err) {
// catches errors both in fetch and response.json
alert(err);
}
}Top-level async error with .catch()
async function f() {
let response = await fetch('http://no-such-url');
}
f().catch(alert);Sending data with fetch (POST)
fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ data }),
})
.then(response => response.json())
.then(result => console.log(result));Fetch options and GET request
let url = 'https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits';
let response = await fetch(url);
if (response.ok) {
let json = await response.json();
} else {
alert("HTTP-Error: " + response.status);
}Loading JSON with async/await
let url = 'https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits';
let response = await fetch(url);
let commits = await response.json();
alert(commits[0].author.login);Source
- The Modern JavaScript Tutorial: https://javascript-tutorial/en.javascript.info
- Async/Await reference: https://javascript.info/async-await
- Fetch API reference: https://javascript.info/fetch
Modern JavaScript 2026 Reference
Up-to-date JavaScript language features, APIs, and best practices relevant for React + Vite + API-driven applications.
Language Version Support
ES2024 Features (Current)
Object.groupBy and Map.groupBy
const users = [
{ name: 'Alice', role: 'admin' },
{ name: 'Bob', role: 'user' },
{ name: 'Charlie', role: 'admin' },
];
// Group by role using Object.groupBy
const groupedByRole = Object.groupBy(users, ({ role }) => role);
// {
// admin: [{name: 'Alice', role: 'admin'}, {name: 'Charlie', role: 'admin'}],
// user: [{name: 'Bob', role: 'user'}]
// }
// Group by role using Map.groupBy
const groupedMap = Map.groupBy(users, ({ role }) => role);
// Map(2) { 'admin' => [...], 'user' => [...] }Promise.withResolvers
// Create promise with explicit resolve/reject functions
const { promise, resolve, reject } = Promise.withResolvers();
setTimeout(() => resolve('Done!'), 1000);
promise.then(result => console.log(result)); // "Done!"ES2023 Features
Non-mutating Array Methods
const numbers = [3, 1, 4, 1, 5];
// New methods that don't mutate original array
const sorted = numbers.toSorted();
const reversed = numbers.toReversed();
const spliced = numbers.toSpliced(2, 1, 99); // remove 1 item at index 2, insert 99
const updated = numbers.with(0, 100); // replace item at index 0
console.log(numbers); // [3, 1, 4, 1, 5] (unchanged)
console.log(sorted); // [1, 1, 3, 4, 5]
console.log(reversed); // [5, 1, 4, 1, 3]
console.log(spliced); // [3, 1, 99, 1, 5]
console.log(updated); // [100, 1, 4, 1, 5]ES2022 Features
Top-level await
// In ES modules (.mjs or with "type": "module")
const config = await fetch('/api/config').then(res => res.json());
export const apiBaseUrl = config.apiBaseUrl;Class Fields and Private Methods
class RecipeService {
#apiBaseUrl = '/api';
#cache = new Map();
async getRecipe(id) {
if (this.#cache.has(id)) {
return this.#cache.get(id);
}
const response = await fetch(`${this.#apiBaseUrl}/recipes/${id}`);
const recipe = await response.json();
this.#cache.set(id, recipe);
return recipe;
}
#validateId(id) {
if (!Number.isInteger(id) || id <= 0) {
throw new Error('Invalid recipe ID');
}
}
}Asynchronous JavaScript Patterns
Promise Composition
// Promise.all - all must succeed
const [users, recipes, stats] = await Promise.all([
fetch('/api/users').then(r => r.json()),
fetch('/api/recipes').then(r => r.json()),
fetch('/api/stats').then(r => r.json()),
]);
// Promise.allSettled - handle partial failures
const results = await Promise.allSettled([
fetch('/api/users').then(r => r.json()),
fetch('/api/recipes').then(r => r.json()),
fetch('/api/notifications').then(r => r.json()),
]);
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`Request ${index} succeeded:`, result.value);
} else {
console.error(`Request ${index} failed:`, result.reason);
}
});
// Promise.any - first successful one wins
const firstResponse = await Promise.any([
fetch('/api-mirror1/data').then(r => r.json()),
fetch('/api-mirror2/data').then(r => r.json()),
fetch('/api-mirror3/data').then(r => r.json()),
]);AbortController for Request Cancellation
const controller = new AbortController();
const signal = controller.signal;
try {
const response = await fetch('/api/recipes', { signal });
const data = await response.json();
console.log(data);
} catch (error) {
if (error.name === 'AbortError') {
console.log('Request was cancelled');
} else {
console.error('Request failed:', error);
}
}
// Cancel request
controller.abort();Web Platform APIs (2026)
URL and URLSearchParams
// Parse URL
const url = new URL('https://example.com/recipes?category=dinner&difficulty=easy');
console.log(url.pathname); // '/recipes'
// Build query params safely
const params = new URLSearchParams();
params.append('category', 'dinner');
params.append('difficulty', 'easy');
params.append('page', '1');
const apiUrl = `/api/recipes?${params.toString()}`;
// '/api/recipes?category=dinner&difficulty=easy&page=1'Intl APIs for Formatting
// Number formatting
const formatter = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
});
console.log(formatter.format(1234.56)); // '$1,234.56'
// Date formatting
const dateFormatter = new Intl.DateTimeFormat('en-US', {
dateStyle: 'medium',
timeStyle: 'short',
});
console.log(dateFormatter.format(new Date()));
// Relative time
const relativeFormatter = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });
console.log(relativeFormatter.format(-2, 'day')); // '2 days ago'
// List formatting
const listFormatter = new Intl.ListFormat('en', {
style: 'long',
type: 'conjunction',
});
console.log(listFormatter.format(['salt', 'pepper', 'olive oil']));
// 'salt, pepper, and olive oil'Structured Clone
const original = {
id: 1,
name: 'Recipe',
ingredients: ['salt', 'pepper'],
metadata: { difficulty: 'easy' },
};
// Deep clone
const clone = structuredClone(original);
clone.metadata.difficulty = 'hard';
console.log(original.metadata.difficulty); // 'easy' (unchanged)
console.log(clone.metadata.difficulty); // 'hard'Error Handling Patterns
Custom Error Classes
class ApiError extends Error {
constructor(message, status, data = null) {
super(message);
this.name = 'ApiError';
this.status = status;
this.data = data;
}
}
class ValidationError extends Error {
constructor(message, fieldErrors = {}) {
super(message);
this.name = 'ValidationError';
this.fieldErrors = fieldErrors;
}
}
async function fetchRecipe(id) {
const response = await fetch(`/api/recipes/${id}`);
if (!response.ok) {
const errorData = await response.json().catch(() => null);
if (response.status === 404) {
throw new ApiError('Recipe not found', 404, errorData);
}
if (response.status === 422) {
throw new ValidationError(
'Validation failed',
errorData?.errors || {}
);
}
throw new ApiError(
`Request failed with status ${response.status}`,
response.status,
errorData
);
}
return response.json();
}Error Boundary Pattern (for React)
function handleAsyncError(error, context = '') {
console.error(`Error in ${context}:`, error);
// Log to monitoring service in production
if (import.meta.env.PROD) {
// Example: sendToMonitoring(error, context);
}
// Return user-friendly message
if (error instanceof ValidationError) {
return {
type: 'validation',
message: error.message,
fieldErrors: error.fieldErrors,
};
}
if (error instanceof ApiError) {
switch (error.status) {
case 401:
return { type: 'auth', message: 'Please sign in again.' };
case 403:
return { type: 'permission', message: 'Access denied.' };
case 404:
return { type: 'not-found', message: 'Resource not found.' };
default:
return { type: 'api', message: 'Server error. Please try again.' };
}
}
return { type: 'unknown', message: 'Something went wrong.' };
}Performance Optimizations
Debounce and Throttle
// Debounce: execute after user stops triggering
function debounce(fn, delay = 300) {
let timeoutId;
return function debounced(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => fn.apply(this, args), delay);
};
}
// Throttle: execute at most once per interval
function throttle(fn, interval = 300) {
let lastCall = 0;
return function throttled(...args) {
const now = Date.now();
if (now - lastCall >= interval) {
lastCall = now;
fn.apply(this, args);
}
};
}
// Usage examples
const debouncedSearch = debounce((query) => {
fetch(`/api/search?q=${encodeURIComponent(query)}`);
}, 500);
const throttledScroll = throttle(() => {
console.log('Scroll position:', window.scrollY);
}, 100);Request Deduplication
const pendingRequests = new Map();
async function deduplicatedFetch(url, options = {}) {
const key = `${url}:${JSON.stringify(options)}`;
if (pendingRequests.has(key)) {
return pendingRequests.get(key);
}
const promise = fetch(url, options)
.then(response => {
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return response.json();
})
.finally(() => {
pendingRequests.delete(key);
});
pendingRequests.set(key, promise);
return promise;
}Security Best Practices
Input Sanitization
function sanitizeHtml(input) {
const div = document.createElement('div');
div.textContent = input;
return div.innerHTML;
}
function validateEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
function validatePassword(password) {
return {
isValid: password.length >= 8,
hasUppercase: /[A-Z]/.test(password),
hasLowercase: /[a-z]/.test(password),
hasNumber: /\d/.test(password),
hasSpecial: /[!@#$%^&*]/.test(password),
};
}Safe Local Storage
class SafeStorage {
static set(key, value) {
try {
localStorage.setItem(key, JSON.stringify(value));
return true;
} catch (error) {
console.error(`Failed to save ${key}:`, error);
return false;
}
}
static get(key, defaultValue = null) {
try {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : defaultValue;
} catch (error) {
console.error(`Failed to read ${key}:`, error);
return defaultValue;
}
}
static remove(key) {
localStorage.removeItem(key);
}
static clear() {
localStorage.clear();
}
}Tooling Recommendations (2026)
Essential Tools
- Runtime: Node.js 20+ (LTS)
- Package Manager: npm, pnpm, or yarn
- Bundler: Vite 6+
- Linter: ESLint 9+
- Formatter: Prettier 3+
- Type Checking: TypeScript 5.7+ (even for JS with JSDoc)
- Testing: Vitest + Playwright
Recommended package.json scripts
{
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"lint": "eslint .",
"format": "prettier --write .",
"type-check": "tsc --noEmit",
"test": "vitest",
"test:e2e": "playwright test"
}
}References
Official Documentation
- MDN JavaScript Guide: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide
- ECMAScript Specification: https://tc39.es/ecma262/
- JavaScript Compatibility: https://caniuse.com/
Key Proposals & Features
- TC39 Proposals: https://github.com/tc39/proposals
- V8 Blog (language updates): https://v8.dev/blog
Best Practices
- Web.dev JavaScript: https://web.dev/learn/javascript/
- JavaScript Info: https://javascript.info/
Related skills
FAQ
What does javascript-development do?
javascript-development is a Claude Code skill for ai & agent building.
When should I use javascript-development?
When you need to helps with ai & agent building tasks., or when javascript-development is a claude code skill for ai & agent building.
What are the main capabilities?
javascript-development; AI & Agent Building; AI-coding skill.