es.davy.ai

Preguntas y respuestas de programación confiables

¿Tienes una pregunta?

Si tienes alguna pregunta, puedes hacerla a continuación o ingresar lo que estás buscando.

Establecer correctamente un tiempo de espera para una solicitud HTTTP en Angular con un Service Worker

Como parte de una nueva funcionalidad que está agregando mi equipo, se me pidió que, cuando una solicitud HTTP Post específica falle (cuando haya poca o ninguna conexión a Internet disponible), vuelva a intentar la solicitud cada X segundos durante un período total de Y segundos. Por ejemplo, cada 1 segundo en un período de 8 segundos.
Este es el código que desarrollé:
“`javascript
return this.carService.saveCar(car)
.pipe(this.isRetryable() ? retryWhen(errors => {
return errors.pipe(mergeMap(response => {
if (response.status === TIMEOUT<em>EXCEPTION</em>CODE || response.status === UNKNOWN<em>EXCEPTION</em>CODE) {
return of(response).pipe(delay(this.saveCarRetryTimeout)) // X segundos
}

<pre><code> return throwError(response);
}));
}) : tap(),
this.isRetryable() ? timeout(this.saveCarProccessTime) : tap(), // Y segundos
tap((carId: number) => {
this.logger.info(saveCarBaseLog.setStatus(LogStatus.COMPLETE));
}),
catchError((err) => {
this.logger.error(saveCarBaseLog).setStatus(LogStatus.FAILED));
return throwError(err);
}));
</code></pre>

“`

La función isRetryable() verifica si tenemos configuradas las variables X e Y, para que no afecte al proceso.

Después de hacerlo y ver que funciona bien tanto en el entorno local como en el de desarrollo, subimos la versión. Al día siguiente nos encontramos con un problema: en los entornos de preproducción y producción, algunos autos se guardan dos veces.
Después de una investigación que hice, parece que este problema proviene del worker del servicio que tenemos. Cuando ocurre un tiempo de espera completo, la solicitud en sí misma se agota, aunque la solicitud FETCH asociada nunca se cancela, lo que provoca un problema cuando la conexión a Internet es lenta (la solicitud FETCH eventualmente tiene éxito y no obtenemos ninguna indicación al respecto).
Realmente estoy perdido en cuanto a qué hacer aquí, así que cualquier ayuda es bienvenida.

No puedo subir una captura de pantalla de la red ya que es una red privada,
Pero en la sección de red en Chrome se ve así:

Solicitud POST – saveCar – XHR – 504 Tiempo de espera
Solicitud POST – (ServiceWorker) saveCar – FETCH – 504 Tiempo de espera
Solicitud POST – saveCar – XHR – 504 Tiempo de espera
Solicitud POST – (ServiceWorker) saveCar – FETCH – 504 Tiempo de espera
Solicitud POST – saveCar – XHR – 504 Tiempo de espera
Solicitud POST – (ServiceWorker) saveCar – FETCH – 200 Éxito (El problemático)

Tags:  , , ,

Answer

  1. Avatar for davy.ai

    Solución para reintentar solicitudes HTTP POST en Angular con Service Worker

    El código proporcionado parece estar reintentando la solicitud HTTP POST cuando hay una conexión lenta o inexistente a Internet durante un período total de Y segundos. Sin embargo, en el entorno de producción, algunos coches se guardan dos veces debido al Service Worker.

    Para resolver este problema, necesitamos modificar el Service Worker para manejar los reintentos de una manera más inteligente.

    Una forma de hacerlo es interceptar la solicitud saliente de HTTP POST dentro del Service Worker, y si agota el tiempo de espera, podríamos almacenarla en IndexedDB con una marca de tiempo. Luego, cuando la red esté disponible nuevamente, podríamos verificar periódicamente IndexedDB para solicitudes agotadas en el tiempo y reintentarlas.

    Aquí tienes un ejemplo básico de cómo se podría hacer esto:

    self.addEventListener('fetch', function(event) {
      if (event.request.method === 'POST') {
        event.respondWith(fetch(event.request).then(function(response) {
          return response;
        }).catch(function(error) {
          // Verificar si el error es un agotamiento de tiempo
          if (error && error.message && error.message.indexOf('timeout') !== -1) {
    
            // Almacenar la solicitud en IndexedDB con una marca de tiempo
            var requestTimestamp = new Date().getTime();
            var dbPromise = idb.openDb('my-db', 1, function(db) {
              if (!db.objectStoreNames.contains('post-requests')) {
                db.createObjectStore('post-requests', {keyPath: 'timestamp'});
              }
            });
            dbPromise.then(function(db) {
              var tx = db.transaction('post-requests', 'readwrite');
              tx.objectStore('post-requests').put({
                request: event.request.clone(),
                timestamp: requestTimestamp
              });
              return tx.complete;
            });
    
            // Devolver una respuesta personalizada que indique que la solicitud se ha almacenado
            return new Response(JSON.stringify({
              status: 'timeout',
              timestamp: requestTimestamp
            }), {
              headers: {'Content-Type': 'application/json'}
            });
          } else {
            return Promise.reject(error);
          }
        }));
      }
    });
    
    // Verificar periódicamente IndexedDB para solicitudes agotadas en el tiempo y reintentarlas
    setInterval(function() {
      var dbPromise = idb.openDb('my-db', 1);
      dbPromise.then(function(db) {
        var index = db.transaction('post-requests').objectStore('post-requests').index('timestamp');
        var cursorRequest = index.openCursor(IDBKeyRange.upperBound(new Date().getTime() - Y * 1000));
        cursorRequest.onsuccess = function(event) {
          var cursor = event.target.result;
          if (cursor) {
            fetch(cursor.value.request).then(function(response) {
              // Eliminar la solicitud de IndexedDB si tiene éxito
              var deleteTx = db.transaction('post-requests', 'readwrite');
              deleteTx.objectStore('post-requests').delete(cursor.value.timestamp);
              deleteTx.complete;
            }).catch(function(error) {
              // No hacer nada si la solicitud aún falla (se volverá a intentar la próxima vez)
            });
            cursor.continue();
          }
        };
      });
    }, X * 1000);
    

    Este código intercepta todas las solicitudes de HTTP POST salientes dentro del Service Worker, y si una agota el tiempo de espera, la almacena en IndexedDB con una marca de tiempo. Luego, verifica periódicamente IndexedDB para solicitudes agotadas en el tiempo y las vuelve a intentar. Si una solicitud tiene éxito, se elimina de IndexedDB.

    Ten en cuenta que este código asume que estás utilizando la biblioteca idb (https://github.com/jakearchibald/idb) para interactuar con IndexedDB.

    Usando este enfoque, deberías poder volver a intentar las solicitudes HTTP POST de una manera más inteligente evitando el problema de guardar los coches dos veces.

Comments are closed.