Async & Promisesmedium
Handle API data flow, loading, errors, and stale requests
Explain how to build reliable frontend API flows: loading states, errors, retries, cancellation, stale response guards, caching, pagination, and optimistic updates.
Asked at Tenable
Answer
Interview framing: Frontend API code is about user experience and correctness. The hard parts are not fetch itself; they are state, races, retries, and stale data.
State model:
- idle: request not started
- loading: request in flight
- success: data available
- error: request failed with recoverable message
- refreshing: old data visible while new data loads
Race condition example:
let latestRequest = 0
async function loadUser(id: string) {
const requestId = ++latestRequest
setState({ status: 'loading' })
try {
const data = await api.getUser(id)
if (requestId !== latestRequest) return
setState({ status: 'success', data })
} catch (error) {
if (requestId !== latestRequest) return
setState({ status: 'error', error })
}
}
Best practices:
- Use AbortController to cancel stale fetches.
- Show useful empty/error states, not only spinners.
- Retry only safe/idempotent operations, ideally with backoff.
- Cache data with a clear invalidation strategy.
- Keep old data visible during refresh when possible.
- Use pagination or virtualization for large result sets.
- For optimistic updates, have rollback behavior on failure.
Security and correctness:
- Never trust API data blindly.
- Validate important shapes at boundaries.
- Handle 401/403 distinctly from 500/network failures.
Source: Tenable frontend job description