#Fire-and-forget HTTP request

1 messages · Page 1 of 1 (latest)

light snow
#

Hello,
It would be nice to be able to do perform fire-and-forget HTTP requests in Deno.
Thanks

brave eagle
#

Not sure I understand. Isn't fetch what you need?

light snow
#

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.

earnest chasm
brave eagle
#

Even just fetch(url) without the await would do.

light snow
#

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.

light snow
#

Mainly serverless and bulk POSTS.

earnest chasm
light snow
#

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.

strange cloud
#

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

light snow
#

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.

brave eagle
#

So the request is guaranteed to already fired on the next line?

light snow
#

Yes.

brave eagle
#

That node example seems to be the same as

const request = new Request(...):
fetch(request);
return request:
light snow
#

No.
As I said : if the process exits right after fetch(), the request won't be sent.

little spruce
#

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
light snow
#

Tested : not working.

earnest chasm
#

How about :

queueMicrotask(() => { console.log('This event loop stack is complete'); });
pliant talon
#

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

light snow
#

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.

median bear
#

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

pliant talon
#

Should be easy enough with http/1.1

light snow
pliant talon
left gazelle
#

@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.

light snow
#

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.

left gazelle
#

Yup. Ain't no API to do it at present, except maybe with node:http.

pliant talon
#

You can connect to port 80 with Deno.connect or connectTls to send the request

#

For tls the port is 443

left gazelle
#

Probably not actually, since this isn't guaranteed to be delivered.

#

Actually maybe it would...

light snow
light snow
light snow
pliant talon
#

Should be fairly easy to implement if you read the doc

left gazelle
#

(Again presuming that this got implemented and was done properly.)

light snow
pliant talon
#

With fire and forget there’s never a guarantee that your request will be received by the server

left gazelle
pliant talon
#

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

light snow
pliant talon
#

We use fire and forget where I work for analytics

#

Who’s the client sending the fire and forget? A browser or the server?

light snow
light snow
pliant talon
light snow
#

Even if it's asynchronous, it's still waiting, just not blocking.

pliant talon
#

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

light snow
#

I'm not. If you don't understand my explanations then pleaaase read the blog post

pliant talon
#

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

light snow
#

Waiting means : the process stays alive until the server returns a status code or fails to do so in a timely manner.

pliant talon
#

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.

light snow
light snow
brave eagle
#

As for making a TCP connection:

const conn = await Deno.connect({ hostname: "golang.org", port: 80, transport: "tcp" });
#

And await conn.write(data).

vocal atlas
#

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

left gazelle
#

(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.)

vocal atlas
#

yea that makes sense 😞
Maybe it was possible to do what OP wants with "good" old XMLHttpRequest?

modern fiber
#

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!

modern fiber
light snow
#

Fire-and-forget does have legitimate use cases and if you can't understand that then nothing you say is useful to me.

modern fiber
#

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.

shadow flax
light snow
#

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.

pliant talon
#

You’re sending these on deno right? The requests?

#

Or are you sending them in the web browser?

shadow flax
#

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

light snow
#

@pliant talon

You’re sending these on deno right? The requests?
Or are you sending them in the web browser?

  1. The browser sends a request to a Deno serverless function ;
  2. the serverless function sends a request to an external API which response I don't care about ;
  3. 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.

shadow flax
#

But the browser can send other requests while it's waiting for data

light snow
#

The browser is only waiting for that request.

shadow flax
#

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

light snow
#

5 seconds is an unacceaptable delay for the user.

shadow flax
#

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

light snow
#

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.

shadow flax
#

It's not a solution that works in Deno Deploy

pliant talon
# light snow <@398682548395311124> > You’re sending these on deno right? The requests? > Or a...

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.

light snow
#

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.

pliant talon
#

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

light snow
pliant talon
#

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.

pliant talon
#

Learn your shit before talking nonsense

light snow
pliant talon
# light snow > what fire-and-forget actually is Fire-and-forget is this : <https://www.sensed...

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/

#

@quick trout Can we close this thread?

light snow
#

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.

pliant talon
#

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.

light snow
#

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 ?

pliant talon
#

I keep telling you, you don't have to wait for a response.

light snow
#

Can I exit without waiting for a response ?

pliant talon
#

Yes. But if you do there is no guarantee the request is sent. That's the beauty of async io.

light snow
#

Well, what I'm asking for is the guarantee that the request is sent without waiting for the answer, i.e. fire-and-forget.

pliant talon
#

Even with your solution you wouldn't get that guarantee.

light snow
#

Actually, it does.