Is it possible to run an async function AFTER the response has been sent ? #14077
-
Hello everyone,
In pseudo-code it looks like this: export default async (req, res) => {
await saveToDb(req.body);
// 👇 notice how there is no await here
fetchLongRunningOperation().then(() => /* This promise never gets resolved */)
res.status(200).end();
} The thing is that in its current form, the However, when I do await for the task, it works: export default async (req, res) => {
await saveToDb(req.body);
// 👇 here is the await
await fetchLongRunningOperation().then(() => /* It works ! */)
res.status(200).end();
} I dived into the Vercel and NextJS docs without finding any information related to this behavior. But because ~5 seconds of waiting is long when starring at a loading signup button, I would like to defer the execution of the task after the response has been sent. Do you know of any ways I could do this? |
Beta Was this translation helpful? Give feedback.
Replies: 10 comments 8 replies
-
So... I missed this bit of information from the limits page
I guess I have my answer. I hope it helps if anyone is wondering why this was happening. |
Beta Was this translation helpful? Give feedback.
-
@dotlouis Have you figured out a workaround for this? Are you simply triggering the background task using a separate API route (that is called by the client but isn't waited on to show the signup as completed)? |
Beta Was this translation helpful? Give feedback.
-
To add to the discussion, with the new Edge Functions, it seems you can now "fire-and-forget" in a middleware:
|
Beta Was this translation helpful? Give feedback.
-
EDIT: This doesn't seem to work.. It allows code to run after res.json, but it also keeps the response from being sent until the code is run, which makes this solution pointless. Can anyone else confirm this? Check out #12573 (comment)
export function withFireAndForget(
handler: (req: NextApiRequest, res: NextApiResponse) => Promise<void>
) {
return async (req: NextApiRequest, res: NextApiResponse) => {
let promiseResolve: () => void = ()=>{throw Error("promiseResolved was not reassigned!")}
const finishedPromise = new Promise<void>((resolve) => {
promiseResolve = resolve;
});
const origEnd = res.end;
res.end = async function newEnd(this: NextApiResponse, ...args: unknown[]) {
await finishedPromise;
//@ts-ignore
return origEnd.call(this, ...args);
};
const result = await handler(req, res);
promiseResolve();
return result;
};
} |
Beta Was this translation helpful? Give feedback.
-
Hello I also have a related question to this topic - (maybe also the same). In the following code example, a mail is being sent via an external service. To speed up the response time of the serverless function, we do not wait (and do not care) about the response.
Now my question: Could it also be in this case, that Alternative would be this at the end of the function:
This way the function is terminated after the promise resolves completely. But this also means that the serverless functions runs longer. What would be the best way? Thanks for the help - @dotlouis. And also an addition - is it a difference - when fire and forgetting to another of your serverless functions, or is it the same issue? E.g.
|
Beta Was this translation helpful? Give feedback.
-
So what is the workaround to have some jobs running after we send the response? What about when we are not in a serverless environment? And also in next 13 we are returning the response which I think then it means we can't run jobs after sending the response? |
Beta Was this translation helpful? Give feedback.
-
@Christopher-Hayes's suggestion For those of you still working on this, here's a stripped down example of what I'm doing for a "fire-and-forget" type of edge function. export const config: NextConfig = {
runtime: 'edge',
}
export default async function handler(request: NextRequest, context: NextFetchEvent) {
context.waitUntil(
// edge function will wait until the promise is resolved/rejected
new Promise<void>(async (resolve, reject) => {
await someLongFunction()
resolve()
})
)
// return the response so the caller doesn't have to wait
return new Response(`Working on it!`);
} Then you can call this edge function from a standard serverless function and look for the 200 response to see if your edge function accepted the request. I went a step further and passed a "callback webhook" that my edge function calls when it's completed with its operations. This helps prevent your serverless functions from timing out while still allowing for certain operations that take a long time, like OpenAI calls 😄 |
Beta Was this translation helpful? Give feedback.
-
I encountered a similar challenge with email sending, and after a thorough analysis, I recommend a customized solution for your Next.js application. This involves creating a dedicated endpoint to efficiently handle the asynchronous email task. It's crucial to note that this approach includes the creation of a new serverless function rather than a simple inner function within the existing serverless function. Important Note: The process of sending an email may take up to 7 seconds. Additionally, it's essential to be mindful of the limitations associated with the Vercel Hobby plan, which offers a maximum function runtime of 10 seconds per execution. For a detailed overview of Vercel function runtimes and limitations, please refer to the documentation here. Ensure that your email task aligns with these constraints to maintain optimal performance within the specified plan. Here's a refined breakdown of the proposed solution:
By introducing this new endpoint to your Next.js application, we enhance modularity and organization, providing a streamlined solution for efficiently managing asynchronous email operations. I welcome any discussions or feedback on this proposed update. |
Beta Was this translation helpful? Give feedback.
-
Here's how I did it: https://github.com/dansdata-se/api/commit/b4f27fc4bda34e282c733bd32ff3fbdb3389cf03 In my case, I wanted to log requests to my database with status code and execution time which is only known after the response is produced. What I did was that I wrapped my API routes with a monkey-patch around // eslint-disable-next-line @typescript-eslint/unbound-method
const originalEnd = res.end;
let endArgs: unknown = undefined;
// @ts-expect-error TypeScript does not like the "unknown"s here
res.end = (...args) => (endArgs = args);
await handler(req, res);
if (endArgs === undefined) {
originalEnd.apply(res);
} else {
// @ts-expect-error TypeScript infers an incorrect type for endArgs
originalEnd.apply(res, endArgs);
} |
Beta Was this translation helpful? Give feedback.
-
You can now!import { waitUntil } from "@vercel/functions";
// within the api handler
// will continue running even after the response as been returned
waitUntil(new Promise(resolve => setTimeout(() => {
console.log("hello world"), 500)
}));
// immediately return your response
return response npm i @vercel/functions https://vercel.com/changelog/waituntil-is-now-available-for-vercel-functions I accidentally found this by reading through the langfuse docs 😆 https://langfuse.com/docs/sdk/typescript/guide#option-1-waiting-for-flushasync-but-returning-immediately |
Beta Was this translation helpful? Give feedback.
You can now!
https://vercel.com/changelog/waituntil-is-now-available-for-vercel-functions
I accidentally found this by reading through the langfuse docs 😆 https://langfuse.com/docs/sdk/typescript/guide#option-1-waiting-for-flushasync-but-returning-immediately