Pengantar
Salah satu pahlawan tanpa tanda jasa di dunia HTML5 adalah XMLHttpRequest
.
Secara ketat, XHR2 bukan HTML5. Namun, ini adalah bagian dari peningkatan inkremental
yang dilakukan vendor browser pada platform inti. Saya menyertakan XHR2 dalam kumpulan fitur baru kami karena XHR2 memainkan peran yang sangat penting dalam aplikasi web yang kompleks saat ini.
Ternyata, teman lama kita ini telah mengalami perubahan besar, tetapi banyak orang tidak mengetahui fitur-fiturnya yang baru. XMLHttpRequest Level 2 memperkenalkan serangkaian kemampuan baru yang mengakhiri hack rumit di aplikasi web kita; hal-hal seperti permintaan lintas origin, peristiwa progres upload, dan dukungan untuk mengupload/mendownload data biner. Hal ini memungkinkan AJAX berfungsi bersama dengan banyak API HTML5 terbaru seperti File System API, Web Audio API, dan WebGL.
Tutorial ini menyoroti beberapa fitur baru di XMLHttpRequest
,
terutama yang dapat digunakan untuk menangani file.
Mengambil data
Mengambil file sebagai blob biner sangat sulit dengan XHR. Secara teknis, hal itu bahkan tidak mungkin. Salah satu trik yang telah didokumentasikan dengan baik melibatkan penggantian jenis mime dengan set karakter yang ditentukan pengguna seperti yang terlihat di bawah.
Cara lama untuk mengambil gambar:
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();
Meskipun cara ini berhasil, yang sebenarnya Anda dapatkan di responseText
bukan blob biner. Ini adalah string biner yang mewakili file gambar.
Kita mengelabui server agar meneruskan data kembali, tanpa diproses.
Meskipun permata kecil ini berfungsi, saya akan menyebutnya ilmu hitam dan menyarankan
agar tidak menggunakannya. Setiap kali Anda menggunakan hack kode karakter dan manipulasi string
untuk memaksa data ke dalam format yang diinginkan, itu adalah masalah.
Menentukan format respons
Pada contoh sebelumnya, kita mendownload gambar sebagai "file" biner
dengan mengganti jenis mime server dan memproses teks respons sebagai string biner.
Sebagai gantinya, mari kita manfaatkan properti responseType
dan response
baru XMLHttpRequest
untuk memberi tahu browser format yang kita inginkan untuk ditampilkan data.
- xhr.responseType
- Sebelum mengirim permintaan, tetapkan
xhr.responseType
ke "text", "arraybuffer", "blob", atau "document", bergantung pada kebutuhan data Anda. Perhatikan, menyetelxhr.responseType = ''
(atau menghapusnya) akan menetapkan respons default ke "text". - xhr.response
- Setelah permintaan berhasil, properti respons xhr akan
berisi data yang diminta sebagai
DOMString
,ArrayBuffer
,Blob
, atauDocument
(bergantung pada apa yang ditetapkan untukresponseType
.)
Dengan fitur baru yang luar biasa ini, kita dapat mengerjakan ulang contoh sebelumnya, tetapi kali ini,
ambil gambar sebagai Blob
, bukan string:
var xhr = new XMLHttpRequest();
xhr.open('GET', '/path/to/image.png', true);
xhr.responseType = 'blob';
xhr.onload = function(e) {
if (this.status == 200) {
// Note: .response instead of .responseText
var blob = new Blob([this.response], {type: 'image/png'});
...
}
};
xhr.send();
Jauh lebih bagus!
Respons ArrayBuffer
ArrayBuffer
adalah penampung panjang tetap generik untuk data biner. Array ini sangat berguna jika Anda
memerlukan buffering data mentah umum, tetapi kekuatan sebenarnya dari array ini adalah
Anda dapat membuat "tampilan" data pokok menggunakan array berjenis JavaScript.
Bahkan, beberapa tampilan dapat dibuat dari satu sumber ArrayBuffer
.
Misalnya, Anda dapat membuat array bilangan bulat 8-bit yang memiliki ArrayBuffer
yang sama dengan array bilangan bulat 32-bit yang ada dari data yang sama. Data pokoknya
tetap sama, kita hanya membuat representasi yang berbeda.
Sebagai contoh, kode berikut mengambil gambar yang sama sebagai ArrayBuffer
,
tetapi kali ini, membuat array bilangan bulat 8-bit tanpa tanda tangan dari buffer data tersebut:
var xhr = new XMLHttpRequest();
xhr.open('GET', '/path/to/image.png', true);
xhr.responseType = 'arraybuffer';
xhr.onload = function(e) {
var uInt8Array = new Uint8Array(this.response); // this.response == uInt8Array.buffer
// var byte3 = uInt8Array[4]; // byte at offset 4
...
};
xhr.send();
Respons blob
Jika Anda ingin bekerja langsung dengan Blob
dan/atau tidak perlu memanipulasi byte file apa pun, gunakan 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();
Blob
dapat digunakan di sejumlah tempat, termasuk menyimpannya ke indexedDB, menulisnya ke File System HTML5, atau membuat URL Blob, seperti yang terlihat dalam contoh ini.
Mengirim data
Kemampuan untuk mendownload data dalam berbagai format memang bagus, tetapi tidak akan membawa kita ke mana pun jika kita tidak dapat mengirim format lengkap ini kembali ke basis utama (server).
XMLHttpRequest
telah membatasi kami untuk mengirim data DOMString
atau Document
(XML) selama beberapa waktu. Jangan khawatir. Metode send()
yang diubah telah diganti untuk menerima salah satu jenis berikut: DOMString
, Document
, FormData
, Blob
, File
, ArrayBuffer
. Contoh di bagian lain
bagian ini menunjukkan pengiriman data menggunakan setiap jenis.
Mengirim data string: xhr.send(DOMString)
function sendText(txt) {
var xhr = new XMLHttpRequest();
xhr.open('POST', '/server', true);
xhr.onload = function(e) {
if (this.status == 200) {
console.log(this.responseText);
}
};
xhr.send(txt);
}
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);
}
sendTextNew('test string');
Tidak ada yang baru di sini, meskipun cuplikan sebelah kanan sedikit berbeda.
Fungsi ini menetapkan responseType='text'
untuk perbandingan. Sekali lagi, menghapus baris tersebut
akan menghasilkan hasil yang sama.
Mengirim formulir: xhr.send(FormData)
Banyak orang mungkin terbiasa menggunakan plugin jQuery
atau library lain untuk menangani pengiriman formulir AJAX. Sebagai gantinya, kita dapat menggunakan FormData
,
jenis data baru lainnya yang dibuat untuk XHR2. FormData
mudah digunakan untuk membuat <form>
HTML secara langsung, di JavaScript.
Formulir tersebut kemudian dapat dikirimkan menggunakan AJAX:
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);
}
Pada dasarnya, kita hanya membuat <form>
secara dinamis dan menambahkan
nilai <input>
ke dalamnya dengan memanggil metode tambahan.
Tentu saja, Anda tidak perlu membuat <form>
dari awal.
Objek FormData
dapat diinisialisasi dari dan HTMLFormElement
yang ada
di halaman. Contoh:
<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'); // Append extra data before send.
var xhr = new XMLHttpRequest();
xhr.open('POST', form.action, true);
xhr.onload = function(e) { ... };
xhr.send(formData);
return false; // Prevent page from submitting.
}
Formulir HTML dapat menyertakan upload file (misalnya, <input type="file">
)
dan FormData
juga dapat menanganinya. Cukup tambahkan file dan browser akan
membuat permintaan multipart/form-data
saat send()
dipanggil:
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);
Mengupload file atau blob: xhr.send(Blob)
Kita juga dapat mengirim data File
atau Blob
menggunakan XHR.
Perlu diingat bahwa semua File
adalah Blob
, sehingga keduanya berfungsi di sini.
Contoh ini membuat file teks baru dari awal menggunakan konstruktor Blob()
dan mengupload Blob
tersebut ke server. Kode ini juga menyiapkan pengendali
untuk memberi tahu pengguna tentang progres upload:
<progress min="0" max="100" value="0">0% complete</progress>
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);
}
upload(new Blob(['hello world'], {type: 'text/plain'}));
Mengupload sekumpulan byte: xhr.send(ArrayBuffer)
Terakhir, kita dapat mengirim ArrayBuffer
sebagai payload 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);
}
Cross-Origin Resource Sharing (CORS)
CORS memungkinkan aplikasi web di satu domain membuat permintaan AJAX lintas domain ke domain lain. Sangat mudah untuk mengaktifkannya, hanya memerlukan satu header respons yang dikirim oleh server.
Mengaktifkan permintaan CORS
Misalnya, aplikasi Anda berada di example.com
dan Anda
ingin mengambil data dari www.example2.com
. Biasanya, jika Anda
mencoba melakukan jenis panggilan AJAX ini, permintaan akan gagal dan browser
akan menampilkan error ketidakcocokan origin. Dengan CORS, www.example2.com
dapat memilih untuk mengizinkan permintaan dari example.com
dengan menambahkan header:
Access-Control-Allow-Origin: http://example.com
Access-Control-Allow-Origin
dapat ditambahkan ke satu resource
di bawah situs atau di seluruh domain. Untuk mengizinkan domain mana pun membuat permintaan kepada Anda, tetapkan:
Access-Control-Allow-Origin: *
Bahkan, situs ini (html5rocks.com) telah mengaktifkan CORS di semua halamannya. Aktifkan
Developer Tools dan Anda akan melihat Access-Control-Allow-Origin
dalam respons kami:
Mengaktifkan permintaan lintas-asal itu mudah, jadi harap aktifkan CORS jika data Anda bersifat publik.
Membuat permintaan lintas-domain
Jika endpoint server telah mengaktifkan CORS, membuat permintaan lintas origin tidak berbeda dengan permintaan XMLHttpRequest
normal. Misalnya,
berikut adalah permintaan yang kini dapat dibuat oleh example.com
ke
www.example2.com
:
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://www.example2.com/hello.json');
xhr.onload = function(e) {
var data = JSON.parse(this.response);
...
}
xhr.send();
Contoh praktis
Mendownload + menyimpan file ke sistem file HTML5
Misalnya, Anda memiliki galeri gambar dan ingin mengambil banyak
gambar, lalu menyimpannya secara lokal menggunakan Sistem File HTML5.
Salah satu cara untuk melakukannya adalah dengan meminta gambar sebagai Blob
dan menulisnya menggunakan 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 = 'blob';
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 blob = new Blob([xhr.response], {type: 'image/png'});
writer.write(blob);
}, onError);
}, onError);
}, onError);
};
xhr.send();
Memotong file dan mengupload setiap bagian
Dengan menggunakan File API, kita dapat meminimalkan pekerjaan untuk mengupload file berukuran besar. Tekniknya adalah memotong upload menjadi beberapa bagian, membuat XHR untuk setiap bagian, dan menggabungkan file di server. Hal ini mirip dengan cara Gmail mengupload lampiran besar dengan sangat cepat. Teknik tersebut juga dapat digunakan untuk mengakali batas permintaan HTTP 32 MB Google App Engine.
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) {
upload(blob.slice(start, end));
start = end;
end = start + BYTES_PER_CHUNK;
}
}, false);
})();
Yang tidak ditampilkan di sini adalah kode untuk merekonstruksi file di server.
Referensi
- Spesifikasi XMLHttpRequest Level 2
- Spesifikasi Cross Origin Resource Sharing (CORS)
- Spesifikasi File API
- Spesifikasi FileSystem API