XMLHttpRequest forma parte de las mejoras incrementales que los creadores de navegadores están implementando a la plataforma base.
XMLHttpRequest de nivel 2 introduce una gran cantidad de nuevas funciones que ponen fin a los problemas de nuestras aplicaciones web, como solicitudes de origen cruzado, eventos de progreso de subidas y compatibilidad con subida/bajada de datos binarios. Esto permite a AJAX trabajar en coordinación con muchas de las API HTML5 más punteras, como API de FileSystem, el API de Web Audio y WebGL.
Recuperar archivos como blob
binario era muy complicado con XHR
. Técnicamente, no era ni siquiera posible. Un truco que se ha documentado mucho implicaba anular el tipo mime con un conjunto de caracteres definido por el usuario, como se muestra a continuación.
La antigua forma de recuperar una imagen:
var xhr = new XMLHttpRequest(); xhr.open('GET', '/path/to/image.png', true);
// Hack to pass bytes through unprocessed. xhr.overrideMimeType('text/plain; charset=x-user-defined');
xhr.onreadystatechange = function(e) { if (this.readyState == 4 && this.status == 200) { var binStr = this.responseText; for (var i = 0, len = binStr.length; i < len; ++i) { var c = binStr.charCodeAt(i); //String.fromCharCode(c & 0xff); var byte = c & 0xff; // byte at offset i } } };
xhr.send();
Aunque funciona, lo que se obtiene realmente en responseText
no es un blob
binario, sino una cadena binaria que representa el archivo de imagen. Estamos engañando al servidor para que devuelva los datos sin procesar.
En lugar de realizar la acción del ejemplo anterior, vamos a aprovechar las nuevas propiedades de XMLHttpRequest (responseType
y response
) para indicar al navegador el formato en el que queremos que nos devuelva los datos.
xhr.responseType
: antes de enviar una solicitud, establece xhr.responseType
en text
, arraybuffer
, blob
o document
, en función de los datos que necesites. Ten en cuenta que si se establece xhr.responseType = ''
(o si se omite), se utilizará la respuesta predeterminada text
.xhr.response
: después de una solicitud correcta, la propiedad response
de xhr
contendrá los datos solicitados como DOMString
, ArrayBuffer
, Blob
o Document
(en función del valor establecido para responseType).Con esto, podemos recuperar la imagen como ArrayBuffer
en lugar de como una cadena. Al transferir el búfer al API BlobBuilder
se crea un Blob
:
BlobBuilder = window.MozBlobBuilder || window.WebKitBlobBuilder || window.BlobBuilder;
var xhr = new XMLHttpRequest(); xhr.open('GET', '/path/to/image.png', true); xhr.responseType = 'arraybuffer';
xhr.onload = function(e) { if (this.status == 200) { var bb = new BlobBuilder(); bb.append(this.response); // Note: not xhr.responseText
var blob = bb.getBlob('image/png'); ... } };
xhr.send();
Si se quiere trabajar directamente con un Blob
o no se necesita manipular ni un solo byte del archivo, utilizaremos xhr
:
responseType='blob': window.URL = window.URL || window.webkitURL; // Take care of vendor prefixes.
var xhr = new XMLHttpRequest(); xhr.open('GET', '/path/to/image.png', true); xhr.responseType = 'blob';
xhr.onload = function(e) { if (this.status == 200) { var blob = this.response;
var img = document.createElement('img'); img.onload = function(e) { window.URL.revokeObjectURL(img.src); // Clean up after yourself. }; img.src = window.URL.createObjectURL(blob); document.body.appendChild(img); ... } };
xhr.send();
Durante algún tiempo, XMLHttpRequest nos ha limitado a enviar datos DOMString
o Document
(XML). Pero eso se acabó. Se ha anulado un método send()
y rediseñado para aceptar todos estos tipos: DOMString, Document, FormData, Blob, File y ArrayBuffer
.
xhr.send(domstring)
sendText('test string'); function sendTextNew(txt) { var xhr = new XMLHttpRequest(); xhr.open('POST', '/server', true); xhr.responseType = 'text'; xhr.onload = function(e) { if (this.status == 200) { console.log(this.response); } }; xhr.send(txt); }
sendText2('test string');
xhr.send(formdata)
function sendForm() { var formData = new FormData(); formData.append('username', 'johndoe'); formData.append('id', 123456);
var xhr = new XMLHttpRequest(); xhr.open('POST', '/server', true); xhr.onload = function(e) { ... };
xhr.send(formData); }
Por supuesto, no es necesario crear un objeto <form>
desde cero. Los objetos FormData
se pueden inicializar a partir de un elemento HTMLFormElement
de la página. Por ejemplo:
<form id="myform" name="myform" action="/server"> <input type="text" name="username" value="johndoe"> <input type="number" name="id" value="123456"> <input type="submit" onclick="return sendForm(this.form);"> </form> function sendForm(form) { var formData = new FormData(form);
formData.append('secret_token', '1234567890');
var xhr = new XMLHttpRequest(); xhr.open('POST', form.action, true); xhr.onload = function(e) { ... };
xhr.send(formData);
return false; // Prevent page from submitting. }
Un formulario HTML puede incluir subidas de archivos (como <input type="file">
) y FormData
también. Simplemente se añade el archivo o los archivos y el navegador construirá una solicitud multipart/form-data cuando se ejecute send()
:
function uploadFiles(url, files) { var formData = new FormData();
for (var i = 0, file; file = files[i]; ++i) { formData.append(file.name, file); }
var xhr = new XMLHttpRequest(); xhr.open('POST', url, true); xhr.onload = function(e) { ... };
xhr.send(formData); // multipart/form-data }
document.querySelector('input[type="file"]').addEventListener('change', function(e) { uploadFiles('/server', this.files); }, false);
También podemos enviar datos de File
o Blob
con XHR
.
En este ejemplo se crea un texto nuevo desde cero con el API lobBuilder y se sube ese
Blob` al servidor. El código también configura un controlador para informar al usuario sobre el progreso de la subida:
function upload(blobOrFile) { var xhr = new XMLHttpRequest(); xhr.open('POST', '/server', true); xhr.onload = function(e) { ... };
// Listen to the upload progress. var progressBar = document.querySelector('progress'); xhr.upload.onprogress = function(e) { if (e.lengthComputable) { progressBar.value = (e.loaded / e.total) * 100; progressBar.textContent = progressBar.value; // Fallback for unsupported browsers. } };
xhr.send(blobOrFile); }
// Take care of vendor prefixes. BlobBuilder = window.MozBlobBuilder || window.WebKitBlobBuilder || window.BlobBuilder;
var bb = new BlobBuilder(); bb.append('hello world');
upload(bb.getBlob('text/plain'));
Y por último, podemos enviar ArrayBuffer
como la carga de XHR.
function sendArrayBuffer() { var xhr = new XMLHttpRequest(); xhr.open('POST', '/server', true); xhr.onload = function(e) { ... };
var uInt8Array = new Uint8Array([1, 2, 3]);
xhr.send(uInt8Array.buffer); }
Supongamos que tienes una galería de imágenes y quieres recuperar un grupo de imágenes para, a continuación, guardarlas localmente con el sistema de archivos HTML5. Una forma de conseguir esto sería solicitar imágenes como ArrayBuffer
, crear un Blob
a partir de los datos y escribir el blob con FileWriter
:
window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem;
function onError(e) { console.log('Error', e); }
var xhr = new XMLHttpRequest(); xhr.open('GET', '/path/to/image.png', true); xhr.responseType = 'arraybuffer';
xhr.onload = function(e) {
window.requestFileSystem(TEMPORARY, 1024 * 1024, function(fs) { fs.root.getFile('image.png', {create: true}, function(fileEntry) { fileEntry.createWriter(function(writer) {
writer.onwrite = function(e) { ... }; writer.onerror = function(e) { ... };
var bb = new BlobBuilder(); bb.append(xhr.response);
writer.write(bb.getBlob('image/png'));
}, onError); }, onError); }, onError); };
xhr.send();
Con las API de archivo, podemos minimizar el trabajo necesario para subir un archivo de gran tamaño. La técnica es dividir el archivo que se va a subir en varios fragmentos, crear un XHR
para cada parte y unir los fragmentos en el servidor. Es similar a la forma en que Gmail sube archivos adjuntos tan grandes en tan poco tiempo.
window.BlobBuilder = window.MozBlobBuilder || window.WebKitBlobBuilder || window.BlobBuilder;
function upload(blobOrFile) { var xhr = new XMLHttpRequest(); xhr.open('POST', '/server', true); xhr.onload = function(e) { ... }; xhr.send(blobOrFile); }
document.querySelector('input[type="file"]').addEventListener('change', function(e) { var blob = this.files[0];
const BYTES_PER_CHUNK = 1024 * 1024; // 1MB chunk sizes. const SIZE = blob.size;
var start = 0; var end = BYTES_PER_CHUNK;
while(start < SIZE) {
if ('mozSlice' in blob) { var chunk = blob.mozSlice(start, end); } else { var chunk = blob.webkitSlice(start, end); }
upload(chunk);
start = end; end = start + BYTES_PER_CHUNK; } }, false);
})();