Skip to content

Early Return

Sometimes you need to respond quickly to an incoming HTTP request, and then do other work after sending the response. Some webhooks, like from Slack, require responses within a couple seconds, but you may need more time than that, ie if you’re building an LLM Slack bot. We recommend accomplishing this today by sending an unawaited HTTP request to a val which does the longer processing.

early-returning.tsRun in Val Town ↗
async function handle(req: Request) {
const new = new URL(req.url)
if (url.path === '/') {
// Send off the relevant data to /process below.
// This `fetch` is not awaited.
fetch(`${req.origin}/process`, {
method: "POST",
body: req.body,
});
// Respond immediately, before the processing is done
return Response.json("ok");
} else if (url.path === '/process) {
// Do the long processing here.
// await it to catch any errors and ensure it completes.
// Nobody is waiting on this request, so we can take as long as we need to.
await doSomeWork(request.body);
return Response.json("done");
}
return new Response("not found", { status: 404 })
}

Other serverless platforms support waitUntil to handle this case more explicitly. We are considering bringing this to Val Town.

While this is a useful technique, it’s important to emphasize that, in general, you should otherwise await every Promise. If you don’t remember to use await with Promises, errors that occur in promises won’t be properly handled and functions may run out-of-order.

For example, if you use fetch to request some resource, but forget to await it, then it won’t throw errors when it fails, and you won’t have the values you expect to have:

export async function handle() {
try {
// Result will be an opaque Promise, not a useful value.
const result = fetch("https://google.com/");
} catch (e) {
// Errors will never be caught here because
// fetch is not awaited.
handleError(e);
}
}

When your HTTP file receives a request, we spin up a process to serve it. We keep that process alive for some time after the response is sent to be ready for quick follow on requests. After some inactivity with no new requests, your HTTP process is terminated. This means that if you fire off Promises without awaiting them, they may or may not finish before the process is killed, depending on how long they take and whether the process serving the HTTP endpoint is being kept alive to serve other traffic. The safest path is to await all Promises, or handle long-running tasks in a new request, as explained above, which keeps the HTTP process alive until it returns.