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.