#Fire-and-forget HTTP request
1 messages · Page 1 of 1 (latest)
Not sure I understand. Isn't fetch what you need?
Fire-and-forget means awaiting only for the request to be sent and not waiting for the response.
Using fetch, even if you only do await fetch() without await (await fetch()).json() or anything, it's still waiting for the response.
const fireForget = (msg: string ) => {
fetch(ServiceURL, {
method: "POST",
body: msg
});
}
fireForget('hello')
Even just fetch(url) without the await would do.
Nope, when the next line is executed, the request still hasn't been sent.
So, if the next line is Deno.exit(), then the request will never be sent.
What's your use-case?
Mainly serverless and bulk POSTS.
Why would you be concerned about a simple await with a timeout handler?
Because when the remote server is down, the time spent until timeout is blocking the client. And when there are multiple requests timing out, the serverless function timeouts as well, thus crashing the client.
So, for non-essential requests, I use fire-and-forget instead.
Is there even a reliable way to do this? Not 100% sure on how TCP works here. SYN, ACK, are you waiting for the (first) ack? Because in HTTP lingo, you still haven’t received an “HTTP ACK”, i.e. 2xx
Here's something that 100% works on NodeJS :
import https from 'node:https';
const fireAndForget = ({
url,
method = 'GET',
headers = {},
data
}) => new Promise((resolve, reject) => {
const
_data = JSON.stringify(data),
request = https.request(url, {
method,
headers: _data ? {
...headers,
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(_data)
} : headers
});
request.on('error', reject);
if(_data)
request.write(_data);
request.end(resolve);
});
Here's the blog post I based this code on : https://www.sensedeep.com/blog/posts/stories/lambda-fast-http.html
This feature is the one I'm missing to do serverless with Deno.
So the request is guaranteed to already fired on the next line?
Yes.
That node example seems to be the same as
const request = new Request(...):
fetch(request);
return request:
No.
As I said : if the process exits right after fetch(), the request won't be sent.
Maybe, just maybe, it is enough to wait for the event loop to have events in the queue that are not older than at the point where you want to call Deno.exit. Example:
fetch(url); // no await
setTimeout(() => { Deno.exit() }, 0); // everything that is enqueued at this point will be executed, but nothing after our exit-timer
Tested : not working.
How about :
queueMicrotask(() => { console.log('This event loop stack is complete'); });
You most likely want to await Promise.all([fetch(), fetch(), fetch(), fetch()])
await fetch() does not wait for the response body
Only the response status and headers
You can also add an abort controller for timeouts
I want not to wait for the response at all.
The point of fire-and-forget is not to wait at all so that the user doesn't experience any delay whether a non-essential request succeeds or fails.
Please try to understand my request instead of working around it. Read the blog post for more context.
Tested : not working.
You can't just 'forget' a request, if you perform a fetch and then exit the process you have essentially aborted the request as far as the server is concerned.
If you want to perform a bulk set of requests in a single process you need to manage them concurrently, Promise.allSettled is most likely the thing you need. You may also find pooledMap useful if you want to limit the total number of concurrent requests... https://deno.land/[email protected]/async/pool.ts?s=pooledMap
Essentially you just want to request something without getting the status code?
Should be easy enough with http/1.1
Yes.
Would you have a code snippet ?
Soon, am at the hospital with my mother
@light snow One (painfully manual) thing you can do is to add an event handler for onunload (IIRC) event and prevent default on it, essentially saying that the process is not allowed to exit yet.
In the event handler you also then bind to the fetch promise and call Deno.exit after that resolves.
When your promise resolves you remove the event handler.
If memory serves that will stop anyone running Deno.exit from stopping your program while your fetch is still running. This cannot stop someone from the outside killing your program, though you can affect that using a signal listener as well, but cannot completely prevent it (SIGKILL cannot be stopped IIRC).
Sorry, beforeunload is the proper event to listen to. Though, I am not 100% sure that it is actually triggered on Deno.exit.
Again, you're trying to workaround my request.
My request is to actually perform fire-and-forget.
And my environment is serverless functions, which timeout I can't bypass, and for the user's sake, don't want to bypass anyway.
Yup. Ain't no API to do it at present, except maybe with node:http.
You can connect to port 80 with Deno.connect or connectTls to send the request
For tls the port is 443
Does https://developer.mozilla.org/en-US/docs/Web/API/Navigator/sendBeacon fit the bill? This doesn't exist in Deno yet but there's a PR open for it.
Probably not actually, since this isn't guaranteed to be delivered.
Actually maybe it would...
When trying in Deno the code that works in Node, the callback function of the request.end method is never fired.
Would you have a code snippet for a POST request using this solution ?
Thanks
Nope, because it doesn't allow waiting for the request to be sent.
Not at home
Should be fairly easy to implement if you read the doc
As long as you can trust that the request does get sent, isn't that enough? The docs do say that the user of the API can reliably trust that the data does get sent.
(Again presuming that this got implemented and was done properly.)
An actual fire-and-forget method allows exiting the process as soon as the method was called.
Meanwhile, if I exit right after calling sendBeacon, the request will never be sent.
With fire and forget there’s never a guarantee that your request will be received by the server
The point of the API, according to the documentation, is to allow for reliably sending diagnostics at the closing of a window in browsers. As I read that, the proper server implementation would indeed guarantee that even if you exit right after calling the method, the request still gets sent.
If you close the connection before receiving a status code there is never a guarantee that the request will be handled
A server, according to spec, is free to close any ongoing jobs it had related to the request
Fire and close is also risky because of packet loss handling, your os might assume that because the connection is closed there’s no need to do packet correction
The smartest thing to do is using fetch and preventing the program from closing before the headers has been received
True : it guarantees that it has been sent without guaranteeing that it has been received.
That's exactly the point of fire-and-forget.
As I said multiple times, fire-and-forget is about ignoring status of non-essential requests.
Which, again, is also explained in the blog post that I linked.
Fire and forget according to what I’ve read is about ignoring the response, not the headers
We use fire and forget where I work for analytics
Who’s the client sending the fire and forget? A browser or the server?
If your analytics API is down : does the request wait a few seconds before timing out ?
If yes then it's not fire-and-forget.
A serverless function, as I also mentioned multiple times.
Nothing “waits”, a handshake is being attempted in the background without the user experiencing any lag at all
Even if it's asynchronous, it's still waiting, just not blocking.
No not at all
It goes on
That’s the beauty of async event loops and network io, you don’t have to wait for the handshake to be made to do other things
Serverless functions also usually doesn’t count wait times between io send/receive towards timing
Sounds to me you’re having an xy problem
I'm not. If you don't understand my explanations then pleaaase read the blog post
What EXACTLY do you mean by waiting? Because your understanding of networking seems limited to me. If we know your definition of waiting we can try to help you solve your ACTUAL problem
Waiting means : the process stays alive until the server returns a status code or fails to do so in a timely manner.
the serverless function doesn’t have to wait for the headers to return a response
There is no guarantee the server has actually received anything until first byte
Your best bet would then be to create a tcp connection and manually send the request to server if you insist on not waiting for first byte
If your networking knowledge is where you say it is, you will have no problem at all doing that.
That's not the point. The point is, regardless of this, the serverless function must continue to run until it does when using fetch, while it wouldn't when using fire-and-forget.
True, that's the point of fire-and-forget.
Then I will.
As for making a TCP connection:
const conn = await Deno.connect({ hostname: "golang.org", port: 80, transport: "tcp" });
And await conn.write(data).
Have you tried something like this if possible?
fetch();
Deno.exit();
You can also try to abort it right after:
const controller = new AbortController();
fetch('url', { signal: controller.signal });
controller.abort();
I'm not sure if either guarantees sending, though
That's basically where they started: The request doesn't get sent in that case.
(Which makes sense: If every fetch synchronously started and sent the request then it would be one of the most expensive calls in commonly used JS.)
yea that makes sense 😞
Maybe it was possible to do what OP wants with "good" old XMLHttpRequest?
This is not as nice as you may think, you should not "fire and forget" promises... But that doesn't mean you can't still do work in parallel.
You sort of have two options. If what you want is really to just do multiple things at the same time then you can always await multiple things:
const p0 = fetch()
const p1 = fetch()
await Promise.all([p0, p1])
You can also, of course use the .next() and .catch() functions to handle the promise result in a non-async/await way. But I would strongly recommend not monitoring or logging the result somehow!
Again, you're trying to workaround my request.
I think you may have to come to terms with the request not being reasonable. Its not really a good idea to do this and by design not going to work.
Fire-and-forget does have legitimate use cases and if you can't understand that then nothing you say is useful to me.
Only from the perspective of a single thread is it useful, at the process level you can't forget about any thing you are doing. When you expand your perspective you will understand the replies you have recieved here and why you are unable to achieve quite what you are asking for.
What if you had your service call itself? Then all the requests are sent at once and there's nothing else to worry about. A form of background tasking
Without fire-and-forget, the second function would still have to wait for those requests to either respond or timeout, only after which it will respond to the first function, and the user will wait even more time for the latter to respond.
Again, please stop trying to workaround my request.
You’re sending these on deno right? The requests?
Or are you sending them in the web browser?
Is your client blocking on this specific endpoint? Could you await it and do other things?
The platform you're using has a limitation so we are just trying to find a solution for you.
The alternative is to self host
@pliant talon
You’re sending these on deno right? The requests?
Or are you sending them in the web browser?
- The browser sends a request to a Deno serverless function ;
- the serverless function sends a request to an external API which response I don't care about ;
- the serverless function replies to the browser.
The process starts at step 1 and exits at step 3.
@shadow flax
Step 3 cannot occur until step 2 is over.
Therefore, because step 2 is a normal request, step 3 only occurs when a response is received.
But if step 2 was a fire-and-forget request, then it wouldn't have to wait.
But the browser can send other requests while it's waiting for data
The browser is only waiting for that request.
You might be able to wrap the function in a set time out to cancel if it takes more than 5 seconds to get a response
And try again
5 seconds is an unacceaptable delay for the user.
Sounds like you need to use a different API then
Or consider how this API and client is built
This does not follow the fail fast design pattern it seems like a software architecture problem over a problem with the run time
The API isn't mine.
It's just an API to which I have to send data but don't need anything in return, and it happens to take too much time to respond. So, fire-and-forget is the solution. That's it. I don't need anything else.
It's not a solution that works in Deno Deploy
So, if you've actually cared for reading Deno's source code. You can see, and measure, that fetch() does not require you to wait at all. So here's the thing, Deno exiting when there's nothing left to do makes complete and full sense. You send rust-land a task by calling fetch. Js-land doesn't care whether you do anything with it. If you don't do anything with the Promise you're given it makes sense that it exits, because the event-loop isn't waiting for anything to happen. However, you can control the exit conditions of Deno to ensure that not awaiting fetch() doesn't exit.
let requestCount = 0;
const myFetch = (...args) => {
requestCount++;
return fetch(...args)
.then(
(response) => {
requestCount--;
return Promise.resolve(response);
},
(error) => {
requestCount--;
return Promise.reject(error);
}
);
}
window.addEventListener("beforeunload", (event) => {
if (requestCount !== 0) {
event.preventDefault();
}
})
Should you also not do anything with the result of the fetch anyway it will be forgotten once headers are received at a process level. Your actual business logic doesn't really have to await anything.
When calling fetch(whateverUrl) it sends the task to rust-land and continues. There's no waiting for anything.
As I answered to all the other members who suggested fetch : if the following line is Deno.exit() then the server will never receive the request.
That's the basic test that I need to pass and that fire-and-forget passes.
Why do you need call Deno.exit()
It makes sense that everything is shutdown upon that call
You have sole control of Deno.exit(), you are responsible for making sure things ends cleanly
Because it's the same behavior a serverless function : the process exits when replying to the browser.
No.
You're having a misinterpretation of what fire-and-forget actually is. The reason people started using fire-and-forget to begin is to avoid having to wait for results where it «wasn't possible» to defer the response. A lá blocking io in something like Java. In javascript this is very different due to the nature of async io with event-loops.
Exit signals and Deno.exit() are very different things.
Learn your shit before talking nonsense
what fire-and-forget actually is
Fire-and-forget is this : https://www.sensedeep.com/blog/posts/stories/lambda-fast-http.html
Fire-and-forget is this : https://simonknott.de/articles/fire-and-forget-http-requests
You get the same results with the code above. Relying on two articles from independent developers is not a source I would trust. If you've actually taken the time to read about fire and forget you'll also come to the same conclusion I've come to. You're trying to apply node.js logic to Deno which is bound to fail anyways. Fire-and-forget is a language agnostic concept with different approaches. In other words, another https://xyproblem.info/
Asking about your attempted solution rather than your actual problem
@quick trout Can we close this thread?
You get the same results with the code above
No, the code above waits for a response, while fire-and-forget is about not waiting.
Fire-and-forget is a language agnostic concept with different approaches.
Of course, I only provided NodeJS examples because JS is my main language and Node is the leading server-side JS platform, but yes I'm requesting to have the same feature on Deno.
In other words, another https://xyproblem.info/
No.
Can we close this thread?
Well, since the only answers I can get nowadays are workarounds, I'll just do it myself.
I'm sorry, but can you not read?
You have sole control of Deno.exit(), you are responsible for making sure things ends cleanly
When calling fetch(whateverUrl) it sends the task to rust-land and continues. There's no waiting for anything.
It waits for a response before exiting.
Meanwhile, what I'm asking is the ability to exit as soon as the request is sent, without waiting for a response (i.e. fire-and-forget).
Can I close now ?
I keep telling you, you don't have to wait for a response.
Can I exit without waiting for a response ?
Yes. But if you do there is no guarantee the request is sent. That's the beauty of async io.
Well, what I'm asking for is the guarantee that the request is sent without waiting for the answer, i.e. fire-and-forget.
Even with your solution you wouldn't get that guarantee.
Actually, it does.