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

  1. Make beforeCache hooks synchronous — perform any mutation directly on the response object and return undefined or false.
  2. Move async side-effects (token refresh, remote config fetch) into the beforeRequest hook, which supports async.
  3. 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

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


AI-assisted analysis of sindresorhus/got@e3924aa1e5 (2026-08-03). Data as JSON: /data/errors/01c08e5af7382925.json. Report an issue: GitHub.