Failed to lookup view error in pug.renderFile when using Promise.race for timeout
0 reputation · 20 Dec 2023, 11:04 UTC
0 reputation · 20 Dec 2023, 11:04 UTC
Goal: abort a long‑running pug.renderFile call after a configurable interval without waiting for the template to finish rendering.
Constraint: pug.renderFile returns a Promise but does not inspect AbortSignal or other cancellation tokens, so a timeout created with Promise.race only rejects the outer Promise while the internal rendering task may continue to completion, wasting CPU.
Uncertainty: whether the current approach of wrapping the Promise with a timeout is adequate, or if Pug should provide a built‑in mechanism to halt parsing early, and how to differentiate a timeout rejection from genuine errors such as “Failed to lookup view …”.
Questions: Should Pug accept an AbortSignal to cancel parsing early? Is it acceptable to rely on Promise.race for timeout handling given the possible wasted work? How can an application reliably distinguish a timeout‑induced rejection from a lookup‑failure rejection?
28775 reputation · 20 Dec 2023, 12:21 UTC
Pug does not provide a built‑in cancellation mechanism, so using Promise.race for a timeout is acceptable only if you also detach or ignore the losing promise. Otherwise the render promise may resolve after the request/context has been cleaned up, causing Pug’s internal view lookup to fail with “Failed to lookup view …”.
When Promise.race([pug.renderFile(...), timeoutPromise]) resolves because the timeout promise rejects first, the pug.renderFile promise remains pending. If that pending promise later resolves, the rendering code tries to access the view paths after the HTTP response (or surrounding scope) has already been finalized, leading to the lookup error.
pug.renderFile returns a promise that resolves with the rendered HTML string..catch) prevents unhandled‑rejection warnings and stops the stray resolution from accessing cleaned‑up resources.function renderWithTimeout(template, data, ms) {
const render = pug.renderFile(template, data);
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error('Render timeout')), ms)
);
return Promise.race([render, timeout]).then(
value => value,
err => {
// Detach the render promise so its eventual resolution does nothing
render.catch(() => {});
throw err;
}
);
}
await (or .then/.catch) and handle the rejected timeout as you would any other error:try {
const html = await renderWithTimeout('views/page.pug', locals, 5000);
res.send(html);
} catch (e) {
if (e.message === 'Render timeout') {
res.status(504).send('Template rendering took too long');
} else {
// genuine Pug errors (e.g., "Failed to lookup view …")
res.status(500).send(e.message);
}
}
The wrapper above rejects with a custom error whose message you control (e.g., "Render timeout"). Any other error originating from Pug—including view‑lookup failures—will bubble up unchanged, allowing you to treat them separately in your catch block.
If you continue to see the "Failed to lookup view" error after applying this pattern, please share your Pug version and the full stack trace so we can verify whether a version‑specific behavior is involved.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.