Version: 1.0.0 Optimize async data fetching with caching, parallel requests, and smart revalidation.
Cache API responses to avoid redundant requests.
app.component("CachedData", {
setup({ signal }) {
const data = signal(null);
const loading = signal(false);
const cache = new Map();
const CACHE_DURATION = 5 * 60 * 1000; // 5 minutes
async function fetchData(id) {
// Check cache first
const cached = cache.get(id);
if (cached && Date.now() - cached.timestamp < CACHE_DURATION) {
data.value = cached.data;
return;
}
loading.value = true;
try {
const response = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`);
const result = await response.json();
// Store in cache
cache.set(id, {
data: result,
timestamp: Date.now()
});
data.value = result;
} catch (err) {
console.error("Fetch failed:", err);
} finally {
loading.value = false;
}
}
function clearCache() {
cache.clear();
data.value = null;
}
return { data, loading, fetchData, clearCache };
},
template: (ctx) => `
<div class="cached-data">
<div class="controls">
${[1, 2, 3, 4, 5].map(id => `
<button key="${id}" @click="() => fetchData(${id})">Post ${id}</button>
`).join("")}
<button @click="clearCache" class="clear">Clear Cache</button>
</div>
${ctx.loading.value ? `
<div class="loading">Loading...</div>
` : ctx.data.value ? `
<article>
<h3>${ctx.data.value.title}</h3>
<p>${ctx.data.value.body}</p>
</article>
` : `
<p>Select a post to load</p>
`}
</div>
`
});
Fetch multiple resources simultaneously.
app.component("Dashboard", {
setup({ signal }) {
const users = signal([]);
const posts = signal([]);
const comments = signal([]);
const loading = signal(true);
const error = signal(null);
async function loadDashboard() {
loading.value = true;
error.value = null;
try {
// Fetch all data in parallel
const [usersRes, postsRes, commentsRes] = await Promise.all([
fetch("https://jsonplaceholder.typicode.com/users?_limit=5"),
fetch("https://jsonplaceholder.typicode.com/posts?_limit=5"),
fetch("https://jsonplaceholder.typicode.com/comments?_limit=5")
]);
// Parse all responses
const [usersData, postsData, commentsData] = await Promise.all([
usersRes.json(),
postsRes.json(),
commentsRes.json()
]);
users.value = usersData;
posts.value = postsData;
comments.value = commentsData;
} catch (err) {
error.value = "Failed to load dashboard data";
} finally {
loading.value = false;
}
}
return {
users, posts, comments, loading, error,
loadDashboard,
onMount: loadDashboard
};
},
template: (ctx) => `
<div class="dashboard">
${ctx.loading.value ? `
<div class="loading">Loading dashboard...</div>
` : ctx.error.value ? `
<div class="error">${ctx.error.value}</div>
` : `
<div class="dashboard-grid">
<section class="card">
<h3>Users (${ctx.users.value.length})</h3>
<ul>
${ctx.users.value.map(u => `<li key="${u.id}">${u.name}</li>`).join("")}
</ul>
</section>
<section class="card">
<h3>Recent Posts (${ctx.posts.value.length})</h3>
<ul>
${ctx.posts.value.map(p => `<li key="${p.id}">${p.title.substring(0, 30)}...</li>`).join("")}
</ul>
</section>
<section class="card">
<h3>Comments (${ctx.comments.value.length})</h3>
<ul>
${ctx.comments.value.map(c => `<li key="${c.id}">${c.name.substring(0, 25)}...</li>`).join("")}
</ul>
</section>
</div>
`}
<button @click="loadDashboard" ${ctx.loading.value ? "disabled" : ""}>
Refresh
</button>
</div>
`
});
Prevent duplicate concurrent requests for the same resource.
Ensure only one request is in flight for any given key:
// Create a deduplication utility
function createDeduplicatedFetcher() {
const inFlight = new Map(); // Track pending requests
return async function dedupedFetch(url, options = {}) {
// Check if request for this URL is already in flight
if (inFlight.has(url)) {
console.log("Reusing in-flight request for:", url);
return inFlight.get(url);
}
// Create the promise and store it
const promise = fetch(url, options)
.then(response => response.json())
.finally(() => {
// Remove from in-flight map when done
inFlight.delete(url);
});
inFlight.set(url, promise);
return promise;
};
}
// Usage
const dedupedFetch = createDeduplicatedFetcher();
app.component("DeduplicatedList", {
setup({ signal }) {
const items = signal([]);
const loading = signal(false);
async function loadItems() {
loading.value = true;
try {
// Multiple calls to this will share the same request
items.value = await dedupedFetch("/api/items");
} finally {
loading.value = false;
}
}
return { items, loading, loadItems, onMount: loadItems };
},
template: (ctx) => `
<div>
<button @click="loadItems">Load Items</button>
<button @click="loadItems">Load Again (deduped)</button>
${ctx.loading.value ? `<p>Loading...</p>` :
`<ul>${ctx.items.value.map(i => `<li key="${i.id}">${i.name}</li>`).join("")}</ul>`
}
</div>
`
});
Combine caching with request deduplication:
function createCachedFetcher(options = {}) {
const {
ttl = 5 * 60 * 1000, // Cache TTL: 5 minutes
maxSize = 100 // Max cache entries
} = options;
const cache = new Map();
const inFlight = new Map();
function cleanup() {
const now = Date.now();
for (const [key, entry] of cache) {
if (now - entry.timestamp > ttl) {
cache.delete(key);
}
}
// Limit cache size (LRU-style: remove oldest)
while (cache.size > maxSize) {
const oldestKey = cache.keys().next().value;
cache.delete(oldestKey);
}
}
return async function cachedFetch(url, options = {}) {
const cacheKey = `${options.method || "GET"}:${url}`;
// Check cache
const cached = cache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < ttl) {
console.log("Cache hit:", url);
return cached.data;
}
// Check in-flight
if (inFlight.has(cacheKey)) {
console.log("Reusing in-flight:", url);
return inFlight.get(cacheKey);
}
// Make request
const promise = fetch(url, options)
.then(async response => {
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const data = await response.json();
// Store in cache
cache.set(cacheKey, { data, timestamp: Date.now() });
cleanup();
return data;
})
.finally(() => {
inFlight.delete(cacheKey);
});
inFlight.set(cacheKey, promise);
return promise;
};
}
// Usage
const cachedFetch = createCachedFetcher({ ttl: 60000 }); // 1 minute cache
app.component("CachedDeduped", {
setup({ signal }) {
const user = signal(null);
async function loadUser(id) {
// First call: fetches from API
// Second call within 1 min: returns cached
// Concurrent calls: shares single request
user.value = await cachedFetch(`/api/users/${id}`);
}
return { user, loadUser };
},
template: (ctx) => `
<div>
${[1, 2, 3].map(id => `
<button key="${id}" @click="() => loadUser(${id})">Load User ${id}</button>
`).join("")}
${ctx.user.value ? `<p>${ctx.user.value.name}</p>` : ""}
</div>
`
});
Return cached data immediately, then revalidate in the background:
function createSWRFetcher(options = {}) {
const { ttl = 60000, staleTime = 5000 } = options;
const cache = new Map();
return async function swr(url, onUpdate) {
const cached = cache.get(url);
const now = Date.now();
// Return stale data immediately if available
if (cached) {
onUpdate(cached.data, { isStale: now - cached.timestamp > staleTime });
// If data is fresh enough, don't revalidate
if (now - cached.timestamp < staleTime) {
return cached.data;
}
}
// Fetch fresh data
try {
const response = await fetch(url);
const data = await response.json();
cache.set(url, { data, timestamp: now });
onUpdate(data, { isStale: false });
return data;
} catch (err) {
// On error, return stale data if available
if (cached) return cached.data;
throw err;
}
};
}
const swr = createSWRFetcher();
app.component("SWRComponent", {
setup({ signal }) {
const data = signal(null);
const isStale = signal(false);
const loading = signal(true);
async function loadData() {
await swr("/api/dashboard", (newData, meta) => {
data.value = newData;
isStale.value = meta.isStale;
loading.value = false;
});
}
return {
data, isStale, loading,
loadData,
onMount: loadData
};
},
template: (ctx) => `
<div class="${ctx.isStale.value ? 'stale' : 'fresh'}">
${ctx.loading.value ? `<p>Loading...</p>` : `
<p>Data: ${JSON.stringify(ctx.data.value)}</p>
${ctx.isStale.value ? `<p class="stale-indicator">Updating...</p>` : ""}
`}
</div>
`,
style: `
.stale { opacity: 0.7; }
.stale-indicator { color: orange; font-size: 12px; }
`
});
| Pattern | Use Case | Key Benefit |
|---|---|---|
| Caching | Frequently accessed data | Faster subsequent loads |
| Parallel fetch | Dashboard, multiple resources | Faster page load |
| Deduplication | Shared resources | Reduce redundant requests |
| SWR | Real-time dashboards | Instant UI, fresh data |
| ← Overview | Resilience Patterns → |