XMLHttpRequest2 में नई तरकीबें

शुरुआती जानकारी

HTML5 की दुनिया का सबसे बेहतरीन हीरो XMLHttpRequest है. सीधे शब्दों में कहें, तो XHR2 HTML5 नहीं है. हालांकि, यह ब्राउज़र वेंडर की ओर से कोर प्लैटफ़ॉर्म पर किए जा रहे बढ़े हुए सुधारों का हिस्सा है. मैं XHR2 को अपने उपहारों के नए बैग में शामिल कर रहा हूं क्योंकि यह आज के जटिल वेब ऐप्लिकेशन का एक अनिष्ट हिस्सा है.

ऐसा लगता है कि हमारा पुराना दोस्त काफ़ी बड़ा बदलाव कर चुका है, लेकिन कई लोगों को इसकी नई सुविधाओं के बारे में जानकारी नहीं है. XMLHttpRequest लेवल 2, कई नई सुविधाएं देता है, जो हमारे वेब ऐप्लिकेशन में जटिल हैक को खत्म कर देती हैं; क्रॉस-ऑरिजिन अनुरोध, प्रोग्रेस इवेंट अपलोड करने, और बाइनरी डेटा को अपलोड/डाउनलोड करने के लिए सहायता जैसी सुविधाएं देती हैं. इनसे AJAX को कई ब्लीडिंग एज HTML5 एपीआई के साथ कॉन्सर्ट में काम करने में मदद मिलती है, जैसे कि File System API, Web Audio API, और WebGL.

इस ट्यूटोरियल में, XMLHttpRequest की कुछ नई सुविधाओं को हाइलाइट किया गया है. खास तौर पर, उन सुविधाओं को हाइलाइट किया गया है जिनका इस्तेमाल फ़ाइलों पर काम करने के लिए किया जा सकता है.

डेटा फ़ेच किया जा रहा है

XHR के लिए, बाइनरी ब्लॉब के तौर पर फ़ाइल को फ़ेच करने में परेशानी हो रही है. तकनीकी रूप से, यह संभव नहीं था. इस तरह की एक ट्रिक के बारे में अच्छी तरह से बताया जा चुका है. इसमें माइम टाइप को उपयोगकर्ता के तय किए गए वर्णसेट से बदलना शामिल है. इसकी जानकारी नीचे दी गई है.

इमेज फ़ेच करने का पुराना तरीका:

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" या "दस्तावेज़" पर सेट करें. ध्यान दें, 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-बिट इंटीजर अरे बनाया जा सकता है, जो उसी डेटा से मौजूदा 32-बिट इंटीजर अरे के ArrayBuffer को शेयर करता है. बुनियादी डेटा पहले जैसा ही रहता है, हम बस उसे अलग-अलग तरह से दिखाते हैं.

उदाहरण के तौर पर, यह नीचे दी गई इमेज, 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 के साथ काम करना है और/या फ़ाइल की किसी भी बाइट में हेर-फेर करने की ज़रूरत नहीं है, तो 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 में सेव करना, HTML5 फ़ाइल सिस्टम में लिखना या Blob यूआरएल बनाना शामिल है, जैसा कि इस उदाहरण में दिखाया गया है.

डेटा भेजा जा रहा है

अलग-अलग फ़ॉर्मैट में डेटा डाउनलोड कर पाना बहुत अच्छी बात है, लेकिन अगर हम इन रिच फ़ॉर्मैट को होम बेस (सर्वर) पर वापस नहीं भेज पाते, तो यह हमारे लिए काफ़ी मददगार साबित होता है. XMLHttpRequest ने कुछ समय के लिए, हम सिर्फ़ DOMString या Document (एक्सएमएल) डेटा नहीं भेज सकते. अब नहीं. नए 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)

कई लोगों को AJAX फ़ॉर्म सबमिशन मैनेज करने के लिए, jQuery प्लगिन या दूसरी लाइब्रेरी का इस्तेमाल करने की आदत हो चुकी है. इसके बजाय, हम FormData का इस्तेमाल कर सकते हैं, जो XHR2 के लिए बनाया गया एक और नया डेटा टाइप है. FormData JavaScript में, तुरंत एचटीएमएल <form> बनाने का आसान तरीका है. इसके बाद, उस फ़ॉर्म को 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> वैल्यू सेट कर रहे हैं. इसके लिए, 'अपेंड करें' तरीका इस्तेमाल करें.

आपको <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.
}

एचटीएमएल फ़ॉर्म में फ़ाइल अपलोड शामिल हो सकते हैं (जैसे कि <input type="file">) और FormData उसे भी मैनेज कर सकता है. बस फ़ाइलें जोड़ें और ब्राउज़र, send() को कॉल किए जाने पर multipart/form-data अनुरोध करेगा:

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)

हम 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);
}

क्रॉस ऑरिजिन रिसॉर्स शेयरिंग (सीओआरएस)

सीओआरएस एक डोमेन पर मौजूद वेब ऐप्लिकेशन को दूसरे डोमेन पर क्रॉस डोमेन AJAX अनुरोध करने की अनुमति देता है. इसे चालू करना काफ़ी आसान है. सर्वर को सिर्फ़ एक रिस्पॉन्स हेडर भेजने के लिए, इसकी ज़रूरत होती है.

सीओआरएस अनुरोध चालू करना

मान लें कि आपका ऐप्लिकेशन example.com पर मौजूद है और आपको www.example2.com से डेटा हासिल करना है. आम तौर पर, अगर आपने इस तरह का AJAX कॉल करने की कोशिश की, तो अनुरोध फ़ेल हो जाएगा और ब्राउज़र ऑरिजिन से मेल न खाने वाली गड़बड़ी दिखाएगा. सीओआरएस की मदद से, www.example2.com सिर्फ़ हेडर जोड़कर example.com के अनुरोधों को अनुमति दे सकता है:

Access-Control-Allow-Origin: http://example.com

Access-Control-Allow-Origin को किसी साइट या पूरे डोमेन में, किसी एक रिसॉर्स में जोड़ा जा सकता है. किसी भी डोमेन को आपसे अनुरोध करने की अनुमति देने के लिए, सेट करें:

Access-Control-Allow-Origin: *

असल में, इस साइट (html5rocks.com) ने अपने सभी पेजों पर सीओआरएस को चालू कर दिया है. डेवलपर टूल चालू करें. इसके बाद, आपको हमारे रिस्पॉन्स में Access-Control-Allow-Origin दिखेगा:

html5rocks.com पर Access-Control-Allow-Origin हेडर
html5rocks.com पर`Access-Control-Allow-Origin` हेडर

क्रॉस-ऑरिजिन अनुरोध को चालू करना आसान है. इसलिए, अगर आपका डेटा सार्वजनिक है, तो कृपया सीओआरएस चालू करें!

क्रॉस-डोमेन अनुरोध करना

अगर सर्वर एंडपॉइंट ने सीओआरएस को चालू किया है, तो क्रॉस-ऑरिजिन अनुरोध करना, सामान्य 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 का अनुरोध करें और 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();

फ़ाइल को स्लाइस करना और उसका हर हिस्सा अपलोड करना

फ़ाइल एपीआई का इस्तेमाल करके, हम बड़ी फ़ाइल अपलोड करने की प्रोसेस को कम कर सकते हैं. तकनीक में अपलोड को कई हिस्सों में बांटना, हर हिस्से के लिए XHR बनाना, और सर्वर पर फ़ाइल को एक साथ रखना है. यह Gmail के बड़े अटैचमेंट को उतनी ही तेज़ी से अपलोड करने के तरीके से मिलता-जुलता है. इस तरह की तकनीक का इस्तेमाल Google App Engine की 32 एमबी अनुरोध की सीमा को पार करने के लिए भी किया जा सकता है.

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);

})();

फ़ाइल को सर्वर पर फिर से बनाने के लिए, कोड की जानकारी यहां नहीं दिखाई जाती.

रेफ़रंस