sindresorhus/got · error · TypeError
beforeCache hooks must be synchronous. The hook returned a P
Error message
beforeCache hooks must be synchronous. The hook returned a Promise, but this hook must return synchronously. If you need async logic, use beforeRequest hook instead.
What it means
Thrown at source/core/index.ts:2025 inside the beforeCache hook driver. got delegates response caching to `cacheable-request`, whose handler is synchronous; therefore beforeCache hooks MUST return synchronously. The driver calls each hook and checks `is.promise(result)` — if the hook returned a Promise (e.g. it was declared `async` or returned an awaitable), got throws this TypeError. The message points users at the async-friendly beforeRequest hook instead, which is run earlier in the pipeline and does support async work.
Source
Thrown at source/core/index.ts:2025
// Call each beforeCache hook with the response
// Hooks can directly mutate the response - mutations take effect immediately
for (const hook of beforeCacheHooks) {
const result = hook(response);
if (result === false) {
// Prevent caching by adding no-cache headers
// Mutate the response directly to add headers
response.headers['cache-control'] = 'no-cache, no-store, must-revalidate';
response.headers.pragma = 'no-cache';
response.headers.expires = '0';
handler(response);
// Don't call remaining hooks - we've decided not to cache
return;
}
if (is.promise(result)) {
// BeforeCache hooks must be synchronous because cacheable-request's handler is synchronous
throw new TypeError('beforeCache hooks must be synchronous. The hook returned a Promise, but this hook must return synchronously. If you need async logic, use beforeRequest hook instead.');
}
if (result !== undefined) {
// Hooks should return false or undefined only
// Mutations work directly - no need to return the response
throw new TypeError('beforeCache hook must return false or undefined. To modify the response, mutate it directly.');
}
// Else: void/undefined = continue
}
} catch (error: unknown) {
const normalizedError = normalizeError(error);
// Convert hook errors to RequestError and propagate
// This is consistent with how other hooks handle errors
if (gotRequest) {
gotRequest._beforeError(normalizedError instanceof RequestError ? normalizedError : new RequestError(normalizedError.message, normalizedError, gotRequest));
// Don't call handler when error was propagated successfully
return;
}View on GitHub (pinned to e3924aa1e5)
Solutions
- Make beforeCache hooks synchronous — perform any mutation directly on the response object and return undefined or false.
- Move async side-effects (token refresh, remote config fetch) into the beforeRequest hook, which supports async.
- If you need data from an async source, pre-fetch it before the request and close over the resolved value in the synchronous beforeCache hook.
Example fix
// before
hooks: {
beforeCache: [async response => {
const token = await refreshToken();
response.headers.authorization = token;
}]
}
// after — sync mutation in beforeCache, async work in beforeRequest
hooks: {
beforeRequest: [(options, url) => options.headers.authorization = currentToken],
beforeCache: [response => { response.headers['x-cache-tag'] = 'v1'; }]
} Defensive patterns
Strategy: validation
Validate before calling
// Reject async beforeCache hooks at registration time.
function assertSyncBeforeCacheHook(hook) {
if (hook.constructor && hook.constructor.name === 'AsyncFunction') {
throw new TypeError('beforeCache hooks must be synchronous — move async logic to beforeRequest.');
}
}
for (const h of options.hooks?.beforeCache ?? []) assertSyncBeforeCacheHook(h); Type guard
function isSyncFunction(fn: Function): boolean {
return fn.constructor && fn.constructor.name !== 'AsyncFunction';
} Try / catch
try {
await got(url, options);
} catch (error) {
if (error instanceof TypeError && /beforeCache hooks must be synchronous/.test(error.message)) {
throw new Error('Move async side-effects from beforeCache to the beforeRequest hook', { cause: error });
}
throw error;
} Prevention
- Declare beforeCache hooks with `function` (not `async`); mutate the response synchronously.
- Pre-fetch any async data before the request and close over the resolved value.
- Move async refresh/lookup logic into the beforeRequest hook, which is async-safe.
When it happens
Trigger: Declaring a beforeCache hook as `async function` or having it return a Promise; using `.then()` inside the hook; calling a cookieJar or DB API that returns a Promise inside the hook.
Common situations: Copying an afterResponse/beforeRequest async hook pattern into beforeCache without realizing the constraint; introducing a caching layer to an existing integration that has async refresh logic; auto-converting functions to async during refactor.
Related errors
- beforeCache hook must return false or undefined. To modify t
- ERR_RETRYING
- The `afterResponse` hook returned an invalid value
- The reassigned stream body must be readable. Ensure you prov
- The `beforeError` hook must return an Error instance. Receiv
AI-assisted analysis of sindresorhus/got@e3924aa1e5 (2026-08-03).
Data as JSON: /data/errors/01c08e5af7382925.json.
Report an issue: GitHub.