Controlar el proceso de la subida con Storage de Firebase

  • Por
Te explicamos cómo realizar un ejemplo más avanzado de upload de archivos a Firebase Storage en el que controlaremos el proceso y estado de la subida.

Uno de los servicios estrella de la nueva actualización de Firebase es el Storage, que nos permite almacenar archivos en una cubeta de almacenamiento en la nube, de modo que estén disponibles para todos los usuarios, visitantes o las personas que nosotros decidamos.

Es una operación que podríamos realizar perfectamente desde cualquier backend, pero la característica en Firebase es que se puede hacer con código Javascript que se ejecutará en el lado del cliente.

En un artículo anterior ya dimos una introducción al Storage de Firebase, por lo que no vamos a explicarlo todo de nuevo. En cambio, vamos a sofisticar nuestro ejemplo de subida de archivos, para implementar diversos comportamientos que no hemos visto todavía pero que seguro que te interesan.

Monitorar y visualizar el estado de la subida

Durante el upload de un fichero es normal que queramos mostrar el progreso, con la típica barra de progreso, que se irá completando a medida que el archivo se envíe al servidor de Firebase, o un valor numérico del porcentaje que lleva la carga. Esto es especialmente importante en archivos grandes, donde el usuario necesita ver alguna respuesta y saber que las cosas están funcionando y cuánto falta para terminar esa subida.

El control de este proceso es tan sencillo como definir una función callback denominada "next" que entregamos a la hora de la definición del evento "state_changed". Todo esto lo vimos en el anterior artículo, por lo que debería sonarte. Solo que en aquella ocasión no entregamos ningun callback "next" por lo que no se implementó esta operativa.

En esta ocasión hemos alterado un poco el código del ejemplo, para organizarlo algo mejor. en el ejemplo del artículo anterior utilizábamos funciones anónimas y ahora hemos preferido que sean funciones normales (con nombre). Mostramos nuestra definición del evento "state_changed", en la que se indican tres callbakcs para realizar acciones en diversos casos.

uploadTask.on('state_changed', registrandoEstadoSubida, errorSubida, finSubida);
  • registrandoEstadoSubida será una función que se ejecutará en cada paso de la carga del fichero. Es donde vamos a poder informar del porcentaje que llevamos.
  • errorSubida es la función que controlará los casos de error.
  • finSubida es la función que se ejecutará cuando termine el upload del fichero.

De estas tres funciones nos interesa registrandoEstadoSubida(), ya que es donde colocamos el código que nos permitirá informar del porcentaje. Esta función recibe lo que se llama un "Upload Task Snapshot" que es una instantánea en la que obtenemos el estado de la subida.

function registrandoEstadoSubida(uploadSnapshot) {
  var calculoPorcentaje = (uploadSnapshot.bytesTransferred / uploadSnapshot.totalBytes) * 100;
  calculoPorcentaje = Math.round(calculoPorcentaje);
  registrarPorcentaje (calculoPorcentaje);
}

La función se encarga de calcular el porcentaje de subida y luego llamar a otra función llamada registrarPorcentaje() que es donde hemos colocado la manipulación del DOM necesaria para mostrar el porcentaje actual.

function registrarPorcentaje(porcentaje) {
  var elMensaje = document.getElementById('mensaje');
  var textoMensaje = '<p>Porcentaje de subida: ' + porcentaje + '%</p>';
  elMensaje.innerHTML = textoMensaje;
}
Nota: Si no intentas subir archivos un poco pesados podrá ocurrir que de 0% pase directamente a 100%. Eso es porque el archivo sube muy rápido y no le da tiempo a Firebase a avisarte de cada estado intermedio de la subida.

Con esto conseguimos ese efecto deseado de mostrar el porcentaje de la carga de un fichero. Lo mostramos con su valor numérico, por ser algo inmediato, pero nada te impediría usar cualquier tipo de interfaz más amistosa para el usuario como una barra de progreso que se va completando a medida que el archivo se carga.

Nota: Al final del artículo tienes un código donde podrás ver perfectamente cómo integrar estas funciones en el contexto de un ejemplo completo.

Controlar la subida

Otra cosa que puede que necesites realizar es implementar los botones para detener momentáneamente una subida, reanudarla o cancelarla. Esto es muy sencillo de realizar gracias al API de Storage de Firebase.

Para implementar estas funcionalidades nos tenemos que apoyar en el "upload task" que recibimos al invocar al método put() del API de storage. Los métodos que tendremos que invocar son:

  • uploadTask.pause(): para pausar un upload.
  • uploadTask.resume(): para reanudar la carga.
  • uploadTask.cancel(): para cancelar la subida.

Para implementar estos controles necesitamos primero los correspondientes botones:

<div id="controles">
  <button id="pausar">Pausar</button>
  <button id="reanudar">Reanudar</button>
  <button id="cancelar">Cancelar</button>   
</div>

Luego definir los manejadores de eventos "click" sobre los botones:

Para el botón de pausar, me tengo que fijar que tenga una subida corriendo en este instante:

//manejadores de eventos para los botones de control de la subida
document.getElementById('pausar').addEventListener('click', function() {
  if(uploadTask && uploadTask.snapshot.state == 'running') {
    uploadTask.pause();
    console.log('pausada');    
  }
});

Para el botón de reanudar me tengo que fijar que un uploadTask esté en estado "paused".

document.getElementById('reanudar').addEventListener('click', function() {
  if(uploadTask && uploadTask.snapshot.state == 'paused') {
    uploadTask.resume();
    console.log('reanudada');    
  }
});

El botón de cancelar se complica algo, porque necesito cancelar tanto cuando la descarga está parada como cuando la descarga está corriendo. Además, para cancerlar un upload que está pausado, tengo que ponerlo de nuevo en estado running, invocando el correspondiente resume().

document.getElementById('cancelar').addEventListener('click', function() {
  if(uploadTask && (uploadTask.snapshot.state == 'paused' || uploadTask.snapshot.state == 'running')) {
    if(uploadTask.snapshot.state == 'paused') {
      uploadTask.resume();
    }
    uploadTask.cancel();
    console.log('cancelada');   
  };
});

Ejemplo completo disponible

Antes de acabar, colocamos el listado de un ejemplo completo, donde podrás apreciar lo que estamos explicando. Ten en cuenta que para que funcione tendrás que cambiar el código de inicialización de Firebase por el de tu propia aplicación.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Subir archivos con Firebase</title>
  <style>
    body {
      font-family: sans-serif;
    }
    #archivo {
      display: none;
    }
    #controles {
      margin: 15px;
      font-size: .8em;
    }
  </style>
</head>
<body>
  <input type="file" id="campoarchivo">

  <div id="controles">
    <button id="pausar">Pausar</button>
    <button id="reanudar">Reanudar</button>
    <button id="cancelar">Cancelar</button>    
  </div>

  <div id="mensaje"></div>

  <div id="archivo">
    Archivo subido: <a href="" id="enlace">Click para ver</a>
  </div>

  <script src="https://www.gstatic.com/firebasejs/3.6.7/firebase.js"></script>
  <script>
    // Initialize Firebase
    var config = {
      apiKey: "AIzaSyDQRPLegmTwj66DFYLRo2WY1qqc2qBazfE",
      authDomain: "tuapp.firebaseapp.com",
      databaseURL: "https://tuapp.firebaseio.com",
      storageBucket: "tuapp.appspot.com",
      messagingSenderId: "48357058751"
    };
    firebase.initializeApp(config);
  </script>
  <script>
    // Servicios de APIs Firebase
    var authService = firebase.auth();
    var storageService = firebase.storage();

    window.onload = function() {
      authService.signInAnonymously()
        .catch(function(error) {
          console.error('Detectado error de autenticación', error);
        });

      //manejador de evento para el input file
      document.getElementById('campoarchivo').addEventListener('change', function(evento){
        evento.preventDefault();
        var archivo  = evento.target.files[0];
        subirArchivo(archivo);
      });

      //manejadores de eventos para los botones de control de la subida
      document.getElementById('pausar').addEventListener('click', function() {
        if(uploadTask && uploadTask.snapshot.state == 'running') {
          uploadTask.pause();
          console.log('pausada');    
        }
      });
      document.getElementById('reanudar').addEventListener('click', function() {
        if(uploadTask && uploadTask.snapshot.state == 'paused') {
          uploadTask.resume();
          console.log('reanudada');    
        }
      });
      document.getElementById('cancelar').addEventListener('click', function() {
        if(uploadTask && (uploadTask.snapshot.state == 'paused' || uploadTask.snapshot.state == 'running')) {
          if(uploadTask.snapshot.state == 'paused') {
            uploadTask.resume();
          }
          uploadTask.cancel();
          console.log('cancelada');   
        };
      });
    
    };

    // defino el uploadTask como variable global, porque lo voy a necesitar
    var uploadTask;
    function subirArchivo(archivo) {
      var refStorage = storageService.ref('ruta/de/la/subida').child(archivo.name);
      uploadTask = refStorage.put(archivo);

      // El evento donde comienza el control del estado de la subida
      uploadTask.on('state_changed', registrandoEstadoSubida, errorSubida, finSubida);

      //Callbacks para controlar los distintos instantes de la subida
      function registrandoEstadoSubida(uploadSnapshot) {
        var calculoPorcentaje = (uploadSnapshot.bytesTransferred / uploadSnapshot.totalBytes) * 100;
        calculoPorcentaje = Math.round(calculoPorcentaje);
        registrarPorcentaje (calculoPorcentaje);
      }
      function errorSubida(err) {
        console.log('Error al subir el archivo', err);
      }
      function finSubida(){
        console.log('Subida completada');
        console.log('el archivo está subido. Su ruta: ', uploadTask.snapshot.downloadURL);
        enlaceSubido(uploadTask.snapshot.downloadURL);
      }

    }

    

    // mostramos el porcentaje en cada instante de la subida
    function registrarPorcentaje(porcentaje) {
      var elMensaje = document.getElementById('mensaje');
      var textoMensaje = '<p>Porcentaje de subida: ' + porcentaje + '%</p>';
      elMensaje.innerHTML = textoMensaje;
    }

    //mostramos el link para acceso al archivo al final de la subida
    function enlaceSubido(enlace) {
      document.getElementById('enlace').href = enlace;
      document.getElementById('archivo').style.display = 'block';
    }

  </script>
</body>
</html>

Autor

Miguel Angel Alvarez

Miguel es fundador de DesarrolloWeb.com y la plataforma de formación online EscuelaIT. Comenzó en el mundo del desarrollo web en el año 1997, transformando su hobby en su trabajo.

Compartir