class DeadlineError extends Error {
constructor(message = 'request deadline exceeded') {
super(message);
this.name = 'DeadlineError';
this.code = 'DEADLINE_EXCEEDED';
}
}
function withDeadline(totalMs, ...extraSignals) {
const budget = AbortSignal.timeout(totalMs);
const signals = [budget, ...extraSignals.filter(Boolean)];
const combined = AbortSignal.any(signals);
const deadlineAt = Date.now() + totalMs;
return {
signal: combined,
remaining() {
return Math.max(0, deadlineAt - Date.now());
}
};
}
async function fetchWithDeadline(url, { signal, perCallMs, ...init } = {}) {
const signals = [signal, perCallMs ? AbortSignal.timeout(perCallMs) : null];
const combined = AbortSignal.any(signals.filter(Boolean));
try {
return await fetch(url, { ...init, signal: combined });
} catch (err) {
if (err && err.name === 'AbortError' && combined.aborted) {
throw new DeadlineError(`fetch to ${url} aborted by deadline`);
}
throw err;
}
}
module.exports = { DeadlineError, withDeadline, fetchWithDeadline };
const { withDeadline, fetchWithDeadline } = require('./deadline');
const CATALOG = process.env.CATALOG_URL;
const PRICING = process.env.PRICING_URL;
async function getProduct(id, { totalMs, clientSignal }) {
const deadline = withDeadline(totalMs, clientSignal);
const catalogRes = await fetchWithDeadline(`${CATALOG}/products/${id}`, {
signal: deadline.signal,
perCallMs: Math.min(400, deadline.remaining()),
headers: { accept: 'application/json' }
});
if (!catalogRes.ok) {
throw new Error(`catalog responded ${catalogRes.status}`);
}
const product = await catalogRes.json();
// Second hop shares the same budget; it only gets what is left.
const priceRes = await fetchWithDeadline(`${PRICING}/prices/${id}`, {
signal: deadline.signal,
perCallMs: Math.min(400, deadline.remaining()),
headers: { accept: 'application/json' }
});
if (!priceRes.ok) {
throw new Error(`pricing responded ${priceRes.status}`);
}
const { amount, currency } = await priceRes.json();
return { ...product, price: { amount, currency } };
}
module.exports = { getProduct };
const express = require('express');
const { getProduct } = require('./productService');
const { DeadlineError } = require('./deadline');
const router = express.Router();
const DEFAULT_TIMEOUT = 1000;
function resolveTimeout(header) {
const parsed = Number.parseInt(header, 10);
if (Number.isNaN(parsed)) return DEFAULT_TIMEOUT;
return Math.min(Math.max(parsed, 100), 5000);
}
router.get('/products/:id', async (req, res) => {
const totalMs = resolveTimeout(req.get('X-Timeout-Ms'));
try {
const product = await getProduct(req.params.id, {
totalMs,
clientSignal: req.signal
});
res.json(product);
} catch (err) {
if (err instanceof DeadlineError) {
res.status(504).json({ error: 'upstream_timeout', budgetMs: totalMs });
return;
}
if (req.signal.aborted) return; // client already gone
res.status(502).json({ error: 'upstream_failure', detail: err.message });
}
});
module.exports = router;
This snippet shows how a Node.js service enforces a hard deadline on the work triggered by a single inbound request, propagating that deadline to every downstream fetch so no dependency can hang the request thread indefinitely. The core idea is that a request budget is a first-class value: one timeout is chosen at the edge, and every network call underneath must live inside the remaining slice of it.
In deadline.js, AbortSignal.timeout(ms) produces a signal that fires an AbortError after ms, and AbortSignal.any([...]) composes several signals so a fetch aborts on whichever fires first — the caller's disconnect, the overall request deadline, or a per-attempt cap. withDeadline wraps this and returns a helper that computes how much of the budget is left via signal.timeout - Date.now(), which is what lets each successive call get a shrinking slice rather than a fresh full timeout. fetchWithDeadline narrows the abort reason: a native timeout surfaces as a DeadlineError so callers can distinguish "we ran out of time" from "the upstream returned 500".
In productService.js, two downstream calls are made against the same deadline. Because both fetch calls share the composed signal, if the first call eats most of the budget the second one inherits only the remainder — the total wall-clock time is bounded regardless of how many hops occur. AbortSignal.any also folds in req.signal, so if the client hangs up the server stops doing pointless work immediately, which matters for expensive fan-out.
In handler.js, the Express route derives the deadline from an optional X-Timeout-Ms header clamped to a safe range, passes req.signal down, and maps a DeadlineError to HTTP 504 while other failures become 502. This separation keeps timeout semantics out of business logic.
The main trade-off is that aborting a fetch does not roll back work already started on the remote side, so downstream handlers should be idempotent. A subtle pitfall is forgetting to forward req.signal: without it, a disconnected client still pays for the full timeout. Reach for this pattern whenever a service fans out to dependencies and must guarantee a bounded response time.
Related snips
export interface RetryOptions {
retries: number;
baseMs: number;
maxMs: number;
signal?: AbortSignal;
onRetry?: (attempt: number, delay: number, err: unknown) => void;
Exponential backoff with jitter for retries
package dbutil
import (
"context"
"github.com/jackc/pgconn"
Retry Postgres serialization failures with bounded attempts
export type Settled<R> =
| { status: 'fulfilled'; value: R }
| { status: 'rejected'; reason: unknown };
export interface ConcurrencyOptions {
limit: number;
Simple concurrency limiter for batch operations
import { randomBytes, createHash } from "crypto";
import jwt from "jsonwebtoken";
import { RefreshTokenStore } from "./store";
const ACCESS_SECRET = process.env.ACCESS_SECRET!;
const ACCESS_TTL = "15m";
JWT access + refresh token rotation (conceptual)
package deps
import (
"net"
"net/http"
"time"
HTTP client tuned for production: timeouts, transport, and connection reuse
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static values = {
url: String,
delay: { type: Number, default: 800 },
Stimulus: autosave draft with Turbo-friendly requests
Share this code
Here's the card — post it anywhere.