{"id":"01c08e5af7382925","repo":"sindresorhus/got","slug":"beforecache-hooks-must-be-synchronous-the-hook-re","errorCode":null,"errorMessage":"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.","messagePattern":"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\\.","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"source/core/index.ts","lineNumber":2025,"sourceCode":"\t\t\t\t\t\t\t// Call each beforeCache hook with the response\n\t\t\t\t\t\t\t// Hooks can directly mutate the response - mutations take effect immediately\n\t\t\t\t\t\t\tfor (const hook of beforeCacheHooks) {\n\t\t\t\t\t\t\t\tconst result = hook(response);\n\n\t\t\t\t\t\t\t\tif (result === false) {\n\t\t\t\t\t\t\t\t\t// Prevent caching by adding no-cache headers\n\t\t\t\t\t\t\t\t\t// Mutate the response directly to add headers\n\t\t\t\t\t\t\t\t\tresponse.headers['cache-control'] = 'no-cache, no-store, must-revalidate';\n\t\t\t\t\t\t\t\t\tresponse.headers.pragma = 'no-cache';\n\t\t\t\t\t\t\t\t\tresponse.headers.expires = '0';\n\t\t\t\t\t\t\t\t\thandler(response);\n\t\t\t\t\t\t\t\t\t// Don't call remaining hooks - we've decided not to cache\n\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tif (is.promise(result)) {\n\t\t\t\t\t\t\t\t\t// BeforeCache hooks must be synchronous because cacheable-request's handler is synchronous\n\t\t\t\t\t\t\t\t\tthrow 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.');\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tif (result !== undefined) {\n\t\t\t\t\t\t\t\t\t// Hooks should return false or undefined only\n\t\t\t\t\t\t\t\t\t// Mutations work directly - no need to return the response\n\t\t\t\t\t\t\t\t\tthrow new TypeError('beforeCache hook must return false or undefined. To modify the response, mutate it directly.');\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t// Else: void/undefined = continue\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} catch (error: unknown) {\n\t\t\t\t\t\t\tconst normalizedError = normalizeError(error);\n\t\t\t\t\t\t\t// Convert hook errors to RequestError and propagate\n\t\t\t\t\t\t\t// This is consistent with how other hooks handle errors\n\t\t\t\t\t\t\tif (gotRequest) {\n\t\t\t\t\t\t\t\tgotRequest._beforeError(normalizedError instanceof RequestError ? normalizedError : new RequestError(normalizedError.message, normalizedError, gotRequest));\n\t\t\t\t\t\t\t\t// Don't call handler when error was propagated successfully\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}","sourceCodeStart":2007,"sourceCodeEnd":2043,"githubUrl":"https://github.com/sindresorhus/got/blob/e3924aa1e53a6ca3eb93a43618ce532442a89b40/source/core/index.ts#L2007-L2043","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nhooks: {\n  beforeCache: [async response => {\n    const token = await refreshToken();\n    response.headers.authorization = token;\n  }]\n}\n\n// after — sync mutation in beforeCache, async work in beforeRequest\nhooks: {\n  beforeRequest: [(options, url) => options.headers.authorization = currentToken],\n  beforeCache: [response => { response.headers['x-cache-tag'] = 'v1'; }]\n}","handlingStrategy":"validation","validationCode":"// Reject async beforeCache hooks at registration time.\nfunction assertSyncBeforeCacheHook(hook) {\n  if (hook.constructor && hook.constructor.name === 'AsyncFunction') {\n    throw new TypeError('beforeCache hooks must be synchronous — move async logic to beforeRequest.');\n  }\n}\nfor (const h of options.hooks?.beforeCache ?? []) assertSyncBeforeCacheHook(h);","typeGuard":"function isSyncFunction(fn: Function): boolean {\n  return fn.constructor && fn.constructor.name !== 'AsyncFunction';\n}","tryCatchPattern":"try {\n  await got(url, options);\n} catch (error) {\n  if (error instanceof TypeError && /beforeCache hooks must be synchronous/.test(error.message)) {\n    throw new Error('Move async side-effects from beforeCache to the beforeRequest hook', { cause: error });\n  }\n  throw error;\n}","preventionTips":["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."],"tags":["hooks","before-cache","caching","synchronous","async"],"analyzedSha":"e3924aa1e53a6ca3eb93a43618ce532442a89b40","analyzedAt":"2026-08-03T19:22:24.770Z","schemaVersion":2}