title: Race between fetch requests
body: <p>I'm working on a tracker that should collect some data on the websites of our clients and send it to our api using fetch request when site users leave the page.</p>
  <p>The idea was to use <a href="https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onbeforeunload" rel="nofollow noreferrer">beforeunload</a> event handler to send the request, but I've read <a href="https://stackoverflow.com/a/20322988/7657853">here</a> that In order to cover most browsers I also need to use <a href="https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onunload" rel="nofollow noreferrer">unload</a> event handler.</p>
  <p>This is the relevant part of tracker code that our clients will put on their websites:</p>
  <pre><code>var requestSent = false;
  function submitData(element_id, url) {
      
      if (!requestSent) {
          
          var data = JSON.stringify({ourobject});
      
      
          fetch(url, {
             method: 'POST',
             headers: {
               'Accept': 'application/json',
               'Content-Type':'application/x-www-form-urlencoded',
             },
            body: data,})
          .then(response =&gt; response.json())
          .then((data) =&gt; {
             console.log('Hello?');
             requestSent = true;
          });
      } 
  }
  
  window.addEventListener('beforeunload', function (e) { submitData(1, &quot;https://oursiteurl/metrics&quot;);});
  window.addEventListener('unload', function(event) {submitData(1, &quot;https://oursiteurl/metrics&quot;); });
  </code></pre>
  <p>I've tested this on chrome and both requests pass, instead of just the first one that is successful, this leads to duplicate data in our database.</p>
  <p>After putting console log in next to the part where requestSent flag is set to true, I realized that part of the code never gets executed.</p>
  <p>If I preserve logs in network tab, it says that both requests are canceled, even though the data gets to our endpoint</p>
  <p><a href="https://i.stack.imgur.com/42kSl.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/42kSl.png" alt="Network tab result" /></a></p>
  <p>Our api is created in Codeigniter, here is the /metrics endpoint</p>
  <pre><code>public function submit () {
        
        $this-&gt;cors();
  
        $response = [
            'status' =&gt; 'error',
            'message' =&gt; 'No data',
        ];
        
        $data = json_decode(file_get_contents('php://input'), true);
        if (empty($data)) {
            echo json_encode($response);exit();
        }
        // process data and do other stuff ...
  </code></pre>
  <p>Cors function:</p>
  <pre><code>private function cors() {
  
        // Allow from any origin
        if (isset($_SERVER['HTTP_ORIGIN'])) {
            // Decide if the origin in $_SERVER['HTTP_ORIGIN'] is one
            // you want to allow, and if so:
            header(&quot;Access-Control-Allow-Origin: {$_SERVER['HTTP_ORIGIN']}&quot;);
            header('Access-Control-Allow-Credentials: true');
            header('Access-Control-Max-Age: 86400');    // cache for 1 day
        }
  
        // Access-Control headers are received during OPTIONS requests
        if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
  
            if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD']))
                // may also be using PUT, PATCH, HEAD etc
                header(&quot;Access-Control-Allow-Methods: GET, POST, OPTIONS&quot;);         
  
            if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']))
                header(&quot;Access-Control-Allow-Headers: {$_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']}&quot;);
  
        }
  
    }
  </code></pre>
  <p>EDIT:</p>
  <p>Thanks to <strong>@CBroe</strong> for suggesting to use the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Navigator/sendBeacon#Browser_compatibility" rel="nofollow noreferrer">Beacon API</a>, using it removed the need for both unload and beforeunload event handlers:</p>
  <p>submitData now looks like this:</p>
  <pre><code>...
  if (navigator.sendBeacon) {
          let beacon = navigator.sendBeacon(url, data);
          console.log( 'Beacon', beacon );
      } else { // fallback for older browsers
          if (!requestSent) {
              console.log( 'Data object from fallback', data );
              var xhr = new XMLHttpRequest();
              xhr.open(&quot;POST&quot;, url, false); // third parameter of `false` means synchronous
              xhr.send(data);
      }
   ... 
  </code></pre>
  <p>Doing it this way allowed me to only keep beforeunload event handler because it works both on ie and chrome:</p>
  <pre><code> window.addEventListener('beforeunload', function (e) { submitData(1, &quot;https://oursiteurl/metrics&quot;);});
  </code></pre>