บทนำ
XMLHttpRequest
เป็นหนึ่งในวีรบุรุษที่ไม่ได้รับการยกย่องในโลก HTML5
ในทางเทคนิคแล้ว XHR2 ไม่ใช่ HTML5 แต่การเปลี่ยนแปลงนี้เป็นส่วนหนึ่งของการปรับปรุงอย่างต่อเนื่องที่ผู้ให้บริการเบราว์เซอร์ทำกับแพลตฟอร์มหลัก เรารวม XHR2 ไว้ในชุดเครื่องมือใหม่ของเราเนื่องจากมีบทบาทสำคัญอย่างยิ่งในเว็บแอปที่ซับซ้อนในปัจจุบัน
แต่ปรากฏว่าเพื่อนเก่าของเราได้รับการปรับโฉมครั้งใหญ่ แต่ผู้คนจำนวนมากยังไม่ทราบถึงฟีเจอร์ใหม่ๆ ของเครื่องมือนี้ XMLHttpRequest ระดับ 2 นำเสนอความสามารถใหม่ๆ มากมายซึ่งจะยุติการแฮ็กที่ซับซ้อนในเว็บแอปของเรา เช่น คำขอข้ามแหล่งที่มา เหตุการณ์ความคืบหน้าในการอัปโหลด และการสนับสนุนการอัปโหลด/ดาวน์โหลดข้อมูลไบนารี ซึ่งช่วยให้ AJAX ทำงานร่วมกับ API ของ HTML5 ล้ำสมัยหลายรายการ เช่น File System API, Web Audio API และ WebGL
บทแนะนำนี้จะไฮไลต์ฟีเจอร์ใหม่บางรายการใน XMLHttpRequest
โดยเฉพาะฟีเจอร์ที่ใช้สำหรับการทำงานกับไฟล์
กำลังดึงข้อมูล
การดึงข้อมูลไฟล์เป็นบล็อกไบนารีนั้นทำได้ยากมากเมื่อใช้ XHR ในทางเทคนิคแล้ว การดำเนินการดังกล่าวเป็นไปไม่ได้ เคล็ดลับหนึ่งที่บันทึกไว้อย่างดีเกี่ยวข้องกับการลบล้างประเภท MIME ด้วยชุดอักขระที่ผู้ใช้กำหนดตามที่แสดงด้านล่าง
วิธีดึงข้อมูลรูปภาพแบบเก่า
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();
แม้ว่าวิธีนี้จะได้ผล แต่สิ่งที่คุณได้รับกลับใน responseText
ไม่ใช่บล็อกไบนารี โดยเป็นสตริงไบนารีที่แสดงไฟล์รูปภาพ
เราหลอกเซิร์ฟเวอร์ให้ส่งข้อมูลกลับโดยไม่มีการประมวลผล
แม้ว่าวิธีนี้จะได้ผล แต่เราขอเรียกว่าเป็นมนต์ดำและแนะนำไม่ให้ใช้ การใช้การแฮ็กโค้ดอักขระและการดัดแปลงสตริงเพื่อบังคับให้ข้อมูลอยู่ในรูปแบบที่ต้องการเป็นปัญหา
การระบุรูปแบบคำตอบ
ในตัวอย่างก่อนหน้านี้ เราได้ดาวน์โหลดรูปภาพเป็น "ไฟล์" แบบไบนารีโดยลบล้างประเภท MIME ของเซิร์ฟเวอร์และประมวลผลข้อความตอบกลับเป็นสตริงไบนารี
แต่ให้ใช้ประโยชน์จากพร็อพเพอร์ตี้ XMLHttpRequest
ใหม่ responseType
และ response
เพื่อแจ้งให้เบราว์เซอร์ทราบว่าเราต้องการให้แสดงผลข้อมูลในรูปแบบใด
- xhr.responseType
- ก่อนส่งคําขอ ให้ตั้งค่า
xhr.responseType
เป็น "text", "arraybuffer", "blob" หรือ "document" โดยขึ้นอยู่กับความต้องการด้านข้อมูล โปรดทราบว่าการตั้งค่าxhr.responseType = ''
(หรือการละเว้น) จะตั้งค่าการตอบกลับเป็น "text" โดยค่าเริ่มต้น - xhr.response
- หลังจากส่งคำขอสำเร็จแล้ว พร็อพเพอร์ตี้คำตอบของ xhr จะมีข้อมูลที่ขอเป็น
DOMString
,ArrayBuffer
,Blob
หรือDocument
(ขึ้นอยู่กับสิ่งที่ตั้งค่าไว้สำหรับresponseType
)
ฟีเจอร์ใหม่ที่ยอดเยี่ยมนี้ช่วยให้เราแก้ไขตัวอย่างก่อนหน้านี้ได้ แต่ครั้งนี้เราจะดึงข้อมูลรูปภาพเป็น Blob
แทนสตริง
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();
ดีกว่ามาก
คำตอบ ArrayBuffer
ArrayBuffer
คือคอนเทนเนอร์ทั่วไปที่มีความยาวคงที่สำหรับข้อมูลไบนารี อาร์เรย์เหล่านี้มีประโยชน์อย่างยิ่งหากคุณต้องการบัฟเฟอร์ข้อมูลดิบทั่วไป แต่ความสามารถที่แท้จริงของอาร์เรย์เหล่านี้คือคุณสามารถสร้าง "มุมมอง" ของข้อมูลพื้นฐานได้โดยใช้อาร์เรย์ที่มีประเภทของ JavaScript
ความจริงแล้ว คุณสามารถสร้างหลายมุมมองจากแหล่งที่มาของArrayBuffer
แหล่งเดียวได้
เช่น คุณอาจสร้างอาร์เรย์จำนวนเต็ม 8 บิตที่ใช้ ArrayBuffer
เดียวกันกับอาร์เรย์จำนวนเต็ม 32 บิตที่มีอยู่จากข้อมูลเดียวกัน ข้อมูลพื้นฐานจะยังคงเหมือนเดิม เราแค่สร้างการนำเสนอข้อมูลในรูปแบบต่างๆ
ตัวอย่างเช่น คำสั่งต่อไปนี้จะดึงข้อมูลรูปภาพเดียวกันเป็น ArrayBuffer
แต่ครั้งนี้จะสร้างอาร์เรย์จำนวนเต็ม 8 บิตแบบไม่ลงนามจากบัฟเฟอร์ข้อมูลนั้น
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();
คำตอบที่เป็น Blob
หากต้องการทำงานกับ Blob
โดยตรงและ/หรือไม่จําเป็นต้องจัดการไบต์ของไฟล์ ให้ใช้ 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
สามารถใช้ได้ในหลายตำแหน่ง เช่น บันทึกลงใน indexedDB, เขียนลงใน File System ของ HTML5 หรือสร้าง URL ของ Blob ดังที่เห็นในตัวอย่างนี้
การส่งข้อมูล
การดาวน์โหลดข้อมูลในรูปแบบต่างๆ เป็นเรื่องที่ดี แต่ก็ไม่ได้ช่วยให้เราบรรลุเป้าหมายหากส่งรูปแบบริชมีเดียเหล่านี้กลับไปยังฐาน (เซิร์ฟเวอร์) ไม่ได้
XMLHttpRequest
จำกัดให้เราส่งข้อมูล DOMString
หรือ Document
(XML) เท่านั้นเป็นระยะเวลาหนึ่ง ไม่ได้แล้ว ระบบได้ลบล้างsend()
วิธี
ที่ปรับปรุงใหม่เพื่อยอมรับประเภทต่อไปนี้
DOMString
, Document
, FormData
, Blob
,
File
, ArrayBuffer
ตัวอย่างในส่วนที่เหลือของส่วนนี้จะแสดงการส่งข้อมูลโดยใช้แต่ละประเภท
การส่งข้อมูลสตริง: 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');
ไม่มีอะไรใหม่ที่นี่ แต่ข้อมูลโค้ดด้านขวาจะแตกต่างออกไปเล็กน้อย
ซึ่งจะตั้งค่า responseType='text'
ไว้สําหรับการเปรียบเทียบ อีกครั้ง การละเว้นบรรทัดนั้นจะให้ผลลัพธ์เดียวกัน
การส่งแบบฟอร์ม: xhr.send(FormData)
ผู้ใช้จํานวนมากอาจคุ้นเคยกับการใช้ปลั๊กอิน jQuery หรือไลบรารีอื่นๆ เพื่อจัดการการส่งแบบฟอร์ม AJAX แต่จะใช้ FormData
แทนได้ ซึ่งเป็นอีกประเภทข้อมูลใหม่ที่สร้างขึ้นสำหรับ XHR2 FormData
สะดวกสําหรับการสร้าง HTML <form>
ขณะใช้งานใน JavaScript
จากนั้นส่งแบบฟอร์มดังกล่าวได้โดยใช้ 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);
}
โดยพื้นฐานแล้ว เรากําลังสร้าง <form>
แบบไดนามิกและเพิ่มค่า <input>
เข้าไปโดยเรียกใช้เมธอด append
แน่นอนว่าคุณไม่จำเป็นต้องสร้าง <form>
ตั้งแต่ต้น
คุณสามารถเริ่มต้นการทำงานของออบเจ็กต์ FormData
จากHTMLFormElement
ที่มีอยู่ในหน้าเว็บ เช่น
<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.
}
แบบฟอร์ม HTML สามารถมีการอัปโหลดไฟล์ (เช่น <input type="file">
) และ FormData
ก็สามารถจัดการกับแบบฟอร์มดังกล่าวได้เช่นกัน เพียงเพิ่มไฟล์ต่อท้าย แล้วเบราว์เซอร์จะสร้างคําขอ multipart/form-data
เมื่อเรียกใช้ 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);
การอัปโหลดไฟล์หรือ Blob: xhr.send(Blob)
นอกจากนี้ เรายังส่งข้อมูล File
หรือ Blob
โดยใช้ XHR ได้ด้วย
โปรดทราบว่า File
ทั้งหมดคือ Blob
ดังนั้นจะใช้ File
หรือ Blob
ก็ได้
ตัวอย่างนี้จะสร้างไฟล์ข้อความใหม่ตั้งแต่ต้นโดยใช้คอนสตรคเตอร์ Blob()
และอัปโหลด Blob
นั้นไปยังเซิร์ฟเวอร์ โค้ดนี้จะตั้งค่าตัวแฮนเดิลเพื่อแจ้งความคืบหน้าของการอัปโหลดให้ผู้ใช้ทราบด้วย
<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'}));
การอัปโหลดกลุ่มไบต์: xhr.send(ArrayBuffer)
สุดท้ายแต่ไม่ท้ายสุด เราส่ง ArrayBuffer
เป็นเพย์โหลดของ 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);
}
กลไกการแชร์ทรัพยากรข้ามโดเมน (CORS)
CORS อนุญาตให้เว็บแอปพลิเคชันในโดเมนหนึ่งส่งคําขอ AJAX ข้ามโดเมนไปยังโดเมนอื่น การเปิดใช้นั้นง่ายมาก เพียงให้เซิร์ฟเวอร์ส่งส่วนหัวการตอบกลับรายการเดียว
การเปิดใช้คําขอ CORS
สมมติว่าแอปพลิเคชันของคุณอยู่ใน example.com
และคุณต้องการดึงข้อมูลจาก www.example2.com
ปกติแล้ว หากคุณพยายามเรียกใช้ AJAX ประเภทนี้ คำขอจะดำเนินการไม่สำเร็จและเบราว์เซอร์จะแสดงข้อผิดพลาดต้นทางไม่ตรงกัน เมื่อใช้ CORS www.example2.com
สามารถเลือกอนุญาตคำขอจาก example.com
ได้โดยเพียงเพิ่มส่วนหัวต่อไปนี้
Access-Control-Allow-Origin: http://example.com
คุณสามารถเพิ่ม Access-Control-Allow-Origin
ลงในทรัพยากรรายการเดียวภายใต้เว็บไซต์หรือทั้งโดเมน หากต้องการอนุญาตให้โดเมนใดก็ได้ส่งคำขอถึงคุณ ให้ตั้งค่าดังนี้
Access-Control-Allow-Origin: *
เว็บไซต์นี้ (html5rocks.com) เปิดใช้ CORS ในหน้าเว็บทั้งหมดแล้ว เปิดเครื่องมือสําหรับนักพัฒนาซอฟต์แวร์ แล้วคุณจะเห็น Access-Control-Allow-Origin
ในคําตอบของเรา
การเปิดใช้คำขอข้ามโดเมนนั้นทำได้ง่ายมาก ดังนั้นโปรดเปิดใช้ CORS หากข้อมูลของคุณเป็นแบบสาธารณะ
การส่งคําขอข้ามโดเมน
หากปลายทางเซิร์ฟเวอร์เปิดใช้ CORS การส่งคำขอข้ามแหล่งที่มาจะเหมือนกับคำขอ XMLHttpRequest
ปกติ ตัวอย่างเช่น ต่อไปนี้คือคำขอที่ example.com
ส่งให้ 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();
ตัวอย่างที่ใช้งานได้จริง
ดาวน์โหลดและบันทึกไฟล์ไปยังระบบไฟล์ HTML5
สมมติว่าคุณมีแกลเลอรีรูปภาพและต้องการนำข้อมูลรูปภาพจำนวนมากมาบันทึกไว้ในเครื่องโดยใช้ระบบไฟล์ HTML5
วิธีหนึ่งในการทำเช่นนี้คือการขอรูปภาพเป็น Blob
s
และเขียนรูปภาพโดยใช้ 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();
การแบ่งไฟล์ออกเป็นส่วนๆ และอัปโหลดแต่ละส่วน
การใช้ File API ช่วยให้เราลดการทำงานในการอัปโหลดไฟล์ขนาดใหญ่ได้ เทคนิคคือแบ่งการอัปโหลดออกเป็นหลายส่วน สร้างขึ้น XHR สําหรับแต่ละส่วน และรวมไฟล์เข้าด้วยกันบนเซิร์ฟเวอร์ ซึ่งคล้ายกับวิธีที่ Gmail อัปโหลดไฟล์แนบขนาดใหญ่อย่างรวดเร็ว เทคนิคดังกล่าวยังใช้เพื่อหลีกเลี่ยงขีดจํากัดคําขอ 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);
})();
สิ่งที่ไม่ได้แสดงที่นี่คือโค้ดในการสร้างไฟล์ขึ้นมาใหม่บนเซิร์ฟเวอร์
ข้อมูลอ้างอิง
- ข้อกําหนด XMLHttpRequest ระดับ 2
- ข้อกำหนดกลไกการแชร์ทรัพยากรข้ามโดเมน (CORS)
- ข้อกำหนดของ File API
- ข้อกำหนดของ FileSystem API