Fazendo upload com controle de progresso com JavaScript

Bom, já falamos sobre as várias formas de fazer Input de arquivos com JavaScript e também sobre como capturar fotos e gravar vídeos e áudios com getUserMedia.

Agora, vamos ver como podemos demonstrar para o usuário que estes dados estão sendo enviados para o servidor.

E vamos usar uma API nova, o XMLHttpRequest... Mas, isso não é novo!

Pois é. A especificação da última versão do XHR nos oferece algumas funcionalidades novas, entre elas, o envio de dados binários (os nossos blobs, discutidos e preparados devidamente nos posts anteriores) e, também, uma API para acompanharmos o progresso do upload.

Progressivo

Faremos isso de forma progressiva para que não tenhamos problemas com navegadores que não venham a suportar essas features.

Para acompanharmos o progresso do upload, vamos nos apoiar na propriedade upload do XHR; portanto, podemos usá-la como referência para feature detection.

Assim, podemos escolher qual será o comportamento de nossa interface enquanto acontece o upload. Dessa forma, nos certificaremos de que o navegador não irá disparar nenhum erro e que a experiência do usuário será agradável.

   if (xhr.upload) {     // exibiremos a barra de progresso   } else {     // podemos exibir alguma animação que fique se repetindo     // enquanto o upload não for finalizado   } 

Função de upload

Digamos que, ao clicar em um determinado botão, nós vamos usar o arquivo já colhido e fazer o upload. Para isso, usaremos um objeto XHR em modo POST e precisaremos dar um "append" do arquivo ao formulário que será enviado ao servidor.

Essa nossa função será uma promise, a ser resolvida assim que o upload finalizar, sendo rejeitada em caso de falha no upload.

Usaremos uma instância de FormData para enviarmos os dados ao servidor.

Neste exemplo, enviaremos os dados do form e arquivos para o path /, mas você precisará apontar este path para a rota em seu servidor que deverá tratar o form submetido.

Para este exemplo, este servidor terá acesso ao arquivo que estamos enviando por meio do nome "the-file", mas sinta-se à vontade para renomear esta variável, e claro, para enviar mais arquivos sob outros nomes. Aos olhos do servidor, este nome está para o arquivo enviado como o name está para os inputs for form.

 function uploadFile (file) {   return new Promise( (resolve, reject) => {     const xhr = new XMLHttpRequest()      let fd = new FormData()     // adicione ao fd as demais informações que você pretende enviar por POST     // ao servidor, além do(s) arquivo(s)         // agora é hora de adicionarmos o(s) arquivo(s)     fd.append('the-file', file) // nome para referência ao arquivo no formulário     xhr.open('post', '/') // path da rota no servidor      xhr.send(fd) // iniciando a requisição, enviando o FormData   } } 

Eventos

É hora de escutarmos alguns eventos para sabermos o estado de nosso XHR. Os mais tradicionais são o onload e o onerror

Antes de nosso .send, vamos acrescentar o seguinte:

     xhr.onerror = reject // em caso de erro, rejeitamos a promise     xhr.onload = event => {       // o envio ocorreu com sucesso       resolve() // resolvemos nossa promise     } 

Acompanhando o progresso

Até aqui, tudo bem. Estamos falando de um XHR tradicional. É hora de acompanharmos o progresso desse upload.

Com acesso a xhr.upload, poderemos usar um evento novo, o onprogress. Este evento será disparado, passando para nós um objeto do tipo Progress que terá as propriedades loaded e total, em que loaded é a quantidade dos bytes que já foram carregados e total é o total de bytes a serem enviados.

Como você deve ter deduzido, sim, precisaremos fazer um pequeno cálculo com essas informações se quisermos exibir a percentagem do upload.

     if (xhr.upload) {       // caso tenhamos acesso a esta informação       xhr.upload.onprogress = progress => {         console.log(Math.round((progress.loaded * 100) / progress.total) + '%')       }     } else {       // tratamento em navegadores que não suportam xhr.upload     } 

Não confunda este evento com o progress do próprio objeto XHR. Este outro evento é dedicado ao progresso do download, não do upload.

E você também pode optar por escutar o evento abort para saber quando o envio ou download foi cancelado.

Concluindo

Legal, né? Mais uma daquelas coisas que costumávamos ouvir o pessoal dizendo que precisaríamos de alguma outra tecnologia para fazer, como o Flash, ActiveX, Applets ou Java. Só pra constar, três desses quatro caras já estão mortos! #LenhaNaFogueira :p

Em resumo, nossa função de upload ficou assim:

 function uploadFile (file) {   return new Promise( (resolve, reject) => {     const xhr = new XMLHttpRequest()      let fd = new FormData()     // adicione ao fd as demais informações que você pretende enviar por POST     // ao servidor, além do(s) arquivo(s)         // agora é hora de adicionarmos o(s) arquivo(s)     fd.append('the-file', file) // nome para referência ao arquivo no formulário     xhr.open('post', '/') // path da rota no servidor      xhr.onerror = reject // em caso de erro, rejeitamos a promise     xhr.onload = event => {       // o envio ocorreu com sucesso       resolve() // resolvemos nossa promise     }      if (xhr.upload) {       // caso tenhamos acesso a esta informação       xhr.upload.onprogress = progress => {         console.log(Math.round((progress.loaded * 100) / progress.total) + '%')       }     } else {       // tratamento em navegadores que não suportam xhr.upload     }      xhr.send(fd) // iniciando a requisição, enviando o FormData   } } 

Não deixe de ler nossos outros artigos sobre as tecnologias relacionadas aqui, para se sentir mais à vontade na hora de trabalhar com essas funcionalidades.