Pengantar
Salah satu pahlawan tanpa tanda jasa di semesta HTML5 adalah XMLHttpRequest
.
Sebenarnya XHR2 bukan HTML5. Namun, ini merupakan bagian dari peningkatan inkremental
yang dilakukan vendor browser pada platform inti. Saya memasukkan XHR2 ke dalam tas barang
baru kami karena alat ini memainkan bagian integral dalam aplikasi web yang kompleks saat ini.
Ternyata teman lama kami punya perubahan besar tetapi banyak orang tidak menyadari fitur barunya. XMLHttpRequest Level 2 memperkenalkan banyak kemampuan baru yang mengakhiri peretasan rumit di aplikasi web kami; fitur-fitur seperti permintaan lintas origin, mengupload peristiwa progres, dan dukungan untuk mengupload/mendownload data biner. Dengan API ini, AJAX dapat berfungsi bersama dengan banyak API HTML5 yang paling canggih seperti File System API, Web Audio API, dan WebGL.
Tutorial ini menyoroti beberapa fitur baru di XMLHttpRequest
,
terutama yang dapat digunakan untuk bekerja dengan file.
Mengambil data
Pengambilan file sebagai blob biner sangat menyulitkan dengan XHR. Secara teknis, hal itu tidak memungkinkan. Salah satu trik yang telah didokumentasikan dengan baik melibatkan penggantian jenis mime dengan charset yang ditentukan pengguna seperti yang terlihat di bawah ini.
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 berfungsi, yang sebenarnya Anda dapatkan di responseText
bukanlah blob biner. Ini adalah string biner yang mewakili file gambar.
Kami menipu server agar meneruskan data kembali, yang belum diproses.
Meskipun permata kecil ini berhasil, saya akan menyebutnya ilmu hitam dan menyarankannya. Setiap kali Anda melakukan peretasan kode karakter dan manipulasi string
untuk memaksa data ke dalam format yang diinginkan, itulah 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 tentang format yang kita inginkan untuk menampilkan data.
- xhr.responseType
- Sebelum mengirim permintaan, tetapkan
xhr.responseType
ke "text", "arraybuffer", "blob", atau "document", bergantung pada kebutuhan data Anda. Perhatikan, menetapkanxhr.responseType = ''
(atau menghapus) 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 yang ditetapkan untukresponseType
.)
Dengan keunikan baru 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 yang tetap dan generik untuk data biner. Fungsi ini sangat berguna jika Anda
memerlukan buffer umum data mentah. Namun, keunggulan sebenarnya di balik cara ini adalah
Anda dapat membuat "tampilan" data pokok menggunakan array yang diketik 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 yang mendasarinya
tetap sama, kita hanya membuat representasinya yang berbeda.
Sebagai contoh, kode berikut mengambil gambar yang sama seperti ArrayBuffer
,
tetapi kali ini, membuat array bilangan bulat 8-bit tanpa label 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, 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 Sistem File HTML5, atau membuat URL Blob, seperti yang terlihat dalam contoh ini.
Mengirim data
Dapat mendownload data dalam berbagai format memang bagus, tetapi hal ini tidak akan membawa kita ke mana pun jika kita tidak dapat mengirim format yang kaya ini kembali ke home base (server).
XMLHttpRequest
telah membatasi kita untuk mengirim data DOMString
atau Document
(XML) selama beberapa waktu. Jangan lagi. Metode send()
yang diubah telah diganti untuk menerima salah satu jenis berikut:
DOMString
, Document
, FormData
, Blob
,
File
, ArrayBuffer
. Contoh di bagian selanjutnya
menunjukkan pengiriman data menggunakan masing-masing 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 yang tepat sedikit berbeda.
Class ini menetapkan responseType='text'
untuk perbandingan. Sekali lagi, menghilangkan garis
tersebut memberikan hasil yang sama.
Mengirimkan formulir: xhr.send(FormData)
Banyak orang mungkin sudah 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 untuk membuat <form>
HTML dengan cepat, di JavaScript.
Formulir tersebut kemudian dapat dikirim 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 melekat pada nilai <input>
ke dalamnya dengan memanggil metode penambahan.
Tentu saja, Anda tidak perlu membuat <form>
dari awal.
Objek FormData
dapat diinisialisasi dari dan HTMLFormElement
yang sudah 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
, jadi 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 potongan 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 diaktifkan, hanya memerlukan satu header respons untuk dikirim oleh server.
Mengaktifkan permintaan CORS
Misalnya aplikasi Anda berada di example.com
dan Anda ingin
menarik data dari www.example2.com
. Biasanya, jika Anda
mencoba melakukan panggilan AJAX jenis ini, permintaan akan gagal dan browser
akan menampilkan error ketidakcocokan origin. Dengan CORS, www.example2.com
dapat memilih untuk mengizinkan permintaan dari example.com
hanya dengan menambahkan header:
Access-Control-Allow-Origin: http://example.com
Access-Control-Allow-Origin
dapat ditambahkan ke resource tunggal
di situs atau di seluruh domain. Untuk mengizinkan domain apa 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 origin itu mudah. Jadi, harap aktifkan CORS jika data Anda bersifat publik.
Membuat permintaan lintas-domain
Jika endpoint server telah mengaktifkan CORS, pembuatan permintaan lintas asal tidak berbeda dengan permintaan XMLHttpRequest
biasa. Misalnya,
berikut adalah permintaan yang sekarang dapat dibuat 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
Misalkan Anda memiliki galeri gambar dan ingin mengambil sekumpulan
gambar, lalu menyimpannya secara lokal menggunakan Sistem File HTML5.
Salah satu cara untuk melakukannya adalah dengan meminta gambar sebagai Blob
dan menuliskannya 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 mengunggah setiap bagiannya
Dengan menggunakan File API, kita dapat meminimalkan pekerjaan untuk mengupload file besar. Tekniknya adalah membagi upload menjadi beberapa bagian, membuat XHR untuk setiap bagian, dan menyatukan file di server. Cara ini mirip dengan cara Gmail mengupload lampiran besar dengan cepat. Teknik tersebut juga dapat digunakan untuk mengatasi batas permintaan http Google App Engine sebesar 32 MB.
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