मैं देशी XHR का प्रचार कैसे करूं?


183

मैं XHR अनुरोध करने के लिए अपने फ्रंटएंड ऐप में (मूल) वादों का उपयोग करना चाहता हूं, लेकिन सभी बड़े पैमाने के ढांचे के बिना।

मैं अपने XHR एक वादा वापस जाने के लिए चाहते हैं, लेकिन यह काम नहीं करता (मुझे दे रही है: Uncaught TypeError: Promise resolver undefined is not a function)

function makeXHRRequest (method, url, done) {
  var xhr = new XMLHttpRequest();
  xhr.open(method, url);
  xhr.onload = function() { return new Promise().resolve(); };
  xhr.onerror = function() { return new Promise().reject(); };
  xhr.send();
}

makeXHRRequest('GET', 'http://example.com')
.then(function (datums) {
  console.log(datums);
});

जवाबों:


369

मैं मान रहा हूँ कि आप जानते हैं कि एक देशी XHR अनुरोध कैसे किया जाता है (आप यहाँ और यहाँ ब्रश कर सकते हैं )

चूंकि कोई भी ब्राउज़र जो मूल वादों का समर्थन करता हैxhr.onload , वह भी समर्थन करेगा , हम सभी onReadyStateChangeटोमफूलरी को छोड़ सकते हैं । चलो एक कदम पीछे लेते हैं और कॉलबैक का उपयोग करते हुए एक बुनियादी XHR अनुरोध फ़ंक्शन के साथ शुरू करते हैं:

function makeRequest (method, url, done) {
  var xhr = new XMLHttpRequest();
  xhr.open(method, url);
  xhr.onload = function () {
    done(null, xhr.response);
  };
  xhr.onerror = function () {
    done(xhr.response);
  };
  xhr.send();
}

// And we'd call it as such:

makeRequest('GET', 'http://example.com', function (err, datums) {
  if (err) { throw err; }
  console.log(datums);
});

हुर्रे! इसमें कुछ भी जटिल नहीं है (जैसे कस्टम हेडर या POST डेटा) लेकिन हमें आगे बढ़ने के लिए पर्याप्त है।

वादा निर्माता

हम ऐसा वादा कर सकते हैं:

new Promise(function (resolve, reject) {
  // Do some Async stuff
  // call resolve if it succeeded
  // reject if it failed
});

वादा निर्माता एक फ़ंक्शन लेता है जिसे दो तर्क पारित किए जाएंगे (चलो उन्हें कॉल करें resolveऔर reject)। आप कॉलबैक के रूप में सोच सकते हैं, एक सफलता के लिए और एक असफलता के लिए। उदाहरण बहुत बढ़िया हैं, आइए makeRequestइस निर्माता के साथ अपडेट करें:

function makeRequest (method, url) {
  return new Promise(function (resolve, reject) {
    var xhr = new XMLHttpRequest();
    xhr.open(method, url);
    xhr.onload = function () {
      if (this.status >= 200 && this.status < 300) {
        resolve(xhr.response);
      } else {
        reject({
          status: this.status,
          statusText: xhr.statusText
        });
      }
    };
    xhr.onerror = function () {
      reject({
        status: this.status,
        statusText: xhr.statusText
      });
    };
    xhr.send();
  });
}

// Example:

makeRequest('GET', 'http://example.com')
.then(function (datums) {
  console.log(datums);
})
.catch(function (err) {
  console.error('Augh, there was an error!', err.statusText);
});

अब हम कई XHR कॉल का पीछा करते हुए (और कॉल .catchपर त्रुटि के लिए ट्रिगर करेंगे) वादों की शक्ति में टैप कर सकते हैं :

makeRequest('GET', 'http://example.com')
.then(function (datums) {
  return makeRequest('GET', datums.url);
})
.then(function (moreDatums) {
  console.log(moreDatums);
})
.catch(function (err) {
  console.error('Augh, there was an error!', err.statusText);
});

हम POST / PUT परम और कस्टम हेडर दोनों को जोड़कर इसे अभी भी और बेहतर बना सकते हैं। हस्ताक्षर के साथ, कई तर्कों के बजाय एक विकल्प ऑब्जेक्ट का उपयोग करें:

{
  method: String,
  url: String,
  params: String | Object,
  headers: Object
}

makeRequest अब कुछ इस तरह दिखता है:

function makeRequest (opts) {
  return new Promise(function (resolve, reject) {
    var xhr = new XMLHttpRequest();
    xhr.open(opts.method, opts.url);
    xhr.onload = function () {
      if (this.status >= 200 && this.status < 300) {
        resolve(xhr.response);
      } else {
        reject({
          status: this.status,
          statusText: xhr.statusText
        });
      }
    };
    xhr.onerror = function () {
      reject({
        status: this.status,
        statusText: xhr.statusText
      });
    };
    if (opts.headers) {
      Object.keys(opts.headers).forEach(function (key) {
        xhr.setRequestHeader(key, opts.headers[key]);
      });
    }
    var params = opts.params;
    // We'll need to stringify if we've been given an object
    // If we have a string, this is skipped.
    if (params && typeof params === 'object') {
      params = Object.keys(params).map(function (key) {
        return encodeURIComponent(key) + '=' + encodeURIComponent(params[key]);
      }).join('&');
    }
    xhr.send(params);
  });
}

// Headers and params are optional
makeRequest({
  method: 'GET',
  url: 'http://example.com'
})
.then(function (datums) {
  return makeRequest({
    method: 'POST',
    url: datums.url,
    params: {
      score: 9001
    },
    headers: {
      'X-Subliminal-Message': 'Upvote-this-answer'
    }
  });
})
.catch(function (err) {
  console.error('Augh, there was an error!', err.statusText);
});

एक अधिक व्यापक दृष्टिकोण में पाया जा सकता MDN

वैकल्पिक रूप से, आप फ़िश एपीआई ( पॉलीफ़िल ) का उपयोग कर सकते हैं ।


3
तुम भी के लिए विकल्प जोड़ने के लिए चाहते हो सकता है responseType, प्रमाणीकरण, साख, timeout... और paramsवस्तुओं का समर्थन करना चाहिए धब्बे / bufferviews और FormDataउदाहरणों
Bergi

4
क्या रिजेक्ट होने पर नई एरर वापस करना बेहतर होगा?
प्रशान्त

1
इसके अतिरिक्त, यह वापसी xhr.statusऔर xhr.statusTextत्रुटि पर कोई मतलब नहीं है , क्योंकि वे उस मामले में खाली हैं।
dqd

2
यह कोड एक चीज़ को छोड़कर, विज्ञापित के रूप में काम करता है। मुझे उम्मीद थी कि एक GET अनुरोध के साथ params पास करने का सही तरीका xhr.send (params) के माध्यम से था। हालाँकि, GET अनुरोध भेजने () विधि के लिए भेजे गए किसी भी मान को अनदेखा करता है। इसके बजाय, उन्हें केवल URL पर ही स्ट्रिंग स्ट्रिंग परम क्वेरी की आवश्यकता है। इसलिए, उपरोक्त विधि के लिए, यदि आप चाहते हैं कि "Params" तर्क को GET अनुरोध पर लागू किया जाए, तो GET बनाम POST को पहचानने के लिए दिनचर्या को संशोधित करने की आवश्यकता होती है, और फिर उन मानों को URL के साथ जोड़ते हैं जो xhr को सौंपे जाते हैं। ।खुला हुआ()।
नाई

1
एक का उपयोग करना चाहिए resolve(xhr.response | xhr.responseText);अधिकांश ब्राउज़र में इस बीच repsonse responseText में है।
हेनोब

50

यह निम्न कोड के रूप में सरल हो सकता है।

ध्यान रखें कि यह कोड केवल rejectकॉलबैक को आग देगा जब onerror( नेटवर्क त्रुटियों केवल) कहा जाता है और नहीं जब HTTP स्थिति कोड एक त्रुटि का संकेत देता है। यह अन्य सभी अपवादों को भी बाहर कर देगा। उन्हें संभालना आप पर निर्भर होना चाहिए, आई.एम.ओ.

इसके अतिरिक्त, rejectकॉलबैक को कॉल करने के लिए अनुशंसित किया जाता है, उदाहरण के लिए Errorऔर स्वयं ईवेंट नहीं, बल्कि सादगी के लिए, जैसा कि मैंने छोड़ दिया है।

function request(method, url) {
    return new Promise(function (resolve, reject) {
        var xhr = new XMLHttpRequest();
        xhr.open(method, url);
        xhr.onload = resolve;
        xhr.onerror = reject;
        xhr.send();
    });
}

और इसे लागू करना यह हो सकता है:

request('GET', 'http://google.com')
    .then(function (e) {
        console.log(e.target.response);
    }, function (e) {
        // handle errors
    });

14
@ मादारूचिहा मुझे लगता है कि यह टीएल है, इसका संस्करण डॉ। यह ओपी को उनके प्रश्न का उत्तर देता है और केवल यही।
पेलेग

यह एक अनुरोध के शरीर कहाँ जाता है?
caub

1
@ नियमित रूप से XHR की तरह ही चलें:xhr.send(requestBody)
Peleg

हाँ, लेकिन आपने अपने कोड में ऐसा क्यों नहीं होने दिया? (चूँकि आप ने विधि का उपयोग किया है)
२३

6
मुझे यह उत्तर पसंद है क्योंकि यह तुरंत काम करने के लिए बहुत सरल कोड प्रदान करता है जो प्रश्न का उत्तर देता है।
स्टीव चामिलार्ड

12

अब जो कोई भी इसे खोजता है, आप उसके लिए फंक्शन का उपयोग कर सकते हैं । इसमें कुछ बहुत अच्छा समर्थन है

fetch('http://example.com/movies.json')
  .then(response => response.json())
  .then(data => console.log(data));

मैंने पहली बार @ SomeKittens के उत्तर का उपयोग किया है, लेकिन फिर पता चला fetchकि यह मेरे लिए बॉक्स से बाहर है :)


2
पुराने ब्राउज़र fetchफ़ंक्शन का समर्थन नहीं करते हैं, लेकिन GitHub ने एक पॉलीफ़िल प्रकाशित किया है
21

1
मैं अनुशंसा नहीं करूंगा fetchक्योंकि यह अभी तक रद्द करने का समर्थन नहीं करता है।
जेम्स डन

2
Fetch API के लिए युक्ति अब रद्द करने के लिए प्रदान करती है। समर्थन अब तक फ़ायरफ़ॉक्स 57 Bugzilla.mozilla.org/show_bug.cgi?id=1378342 और Edge 16 में भेज दिया गया है। डेमो: fetch-abort-demo-edge.glitch.me & mdn.github.io/dom-examples/abort/ -पपी । और खुले क्रोम और वेबकिट फ़ीचर बग्स बगसच्रोमियम . org / p/ chromium / issues / detail ? id= 750599 & bugs.webkit.org/show_bug.cgi?id=174980 हैं । कैसे करें: Developers.google.com/web/updates/2017/09/abortable-fetch & developer.mozilla.org/en-US/docs/Web/API/AbortSignal#Examples
sideshowarker

पर जवाब stackoverflow.com/questions/31061838/... है रद्द करने योग्य न लाएं कोड उदाहरण है कि अब तक पहले से ही फ़ायरफ़ॉक्स 57+ और एज में 16+ काम करता है
sideshowbarker

1
@ microo8 भ्रूण का उपयोग करके एक सरल उदाहरण देना अच्छा होगा, और यहाँ लगता है कि इसे लगाने के लिए एक अच्छी जगह है।
जुपॉ

8

मुझे लगता है कि हम शीर्ष उत्तर को और अधिक लचीला और पुन: प्रयोज्य बना सकते हैं, ताकि वह XMLHttpRequestवस्तु का निर्माण न कर सके । ऐसा करने का एकमात्र लाभ यह है कि हमें इसे करने के लिए कोड की 2 या 3 पंक्तियां स्वयं नहीं लिखनी हैं, और इसमें एपीआई की कई विशेषताओं की तरह हमारी पहुंच को दूर करने का बहुत बड़ा दोष है, जैसे हेडर लगाना। यह उस कोड से मूल ऑब्जेक्ट के गुणों को भी छिपाता है जो प्रतिक्रिया (दोनों सफलताओं और त्रुटियों के लिए) को संभालने वाला है। इसलिए हम केवल XMLHttpRequestऑब्जेक्ट को इनपुट के रूप में स्वीकार करके और परिणाम के रूप में इसे पारित करके अधिक लचीला, अधिक व्यापक रूप से लागू फ़ंक्शन बना सकते हैं ।

यह फ़ंक्शन अनियंत्रित XMLHttpRequestऑब्जेक्ट को एक वादा में परिवर्तित करता है, जो गैर-200 स्थिति कोड को डिफ़ॉल्ट रूप से त्रुटि के रूप में मानता है:

function promiseResponse(xhr, failNon2xx = true) {
    return new Promise(function (resolve, reject) {
        // Note that when we call reject, we pass an object
        // with the request as a property. This makes it easy for
        // catch blocks to distinguish errors arising here
        // from errors arising elsewhere. Suggestions on a 
        // cleaner way to allow that are welcome.
        xhr.onload = function () {
            if (failNon2xx && (xhr.status < 200 || xhr.status >= 300)) {
                reject({request: xhr});
            } else {
                resolve(xhr);
            }
        };
        xhr.onerror = function () {
            reject({request: xhr});
        };
        xhr.send();
    });
}

एपीआई Promiseके लचीलेपन का त्याग किए बिना, यह फ़ंक्शन स्वाभाविक रूप से एस की एक श्रृंखला में फिट बैठता XMLHttpRequestहै:

Promise.resolve()
.then(function() {
    // We make this a separate function to avoid
    // polluting the calling scope.
    var xhr = new XMLHttpRequest();
    xhr.open('GET', 'https://stackoverflow.com/');
    return xhr;
})
.then(promiseResponse)
.then(function(request) {
    console.log('Success');
    console.log(request.status + ' ' + request.statusText);
});

catchनमूना कोड को सरल रखने के लिए ऊपर छोड़ दिया गया था। आपके पास हमेशा एक होना चाहिए, और निश्चित रूप से हम कर सकते हैं:

Promise.resolve()
.then(function() {
    var xhr = new XMLHttpRequest();
    xhr.open('GET', 'https://stackoverflow.com/doesnotexist');
    return xhr;
})
.then(promiseResponse)
.catch(function(err) {
    console.log('Error');
    if (err.hasOwnProperty('request')) {
        console.error(err.request.status + ' ' + err.request.statusText);
    }
    else {
        console.error(err);
    }
});

और HTTP स्थिति कोड हैंडलिंग को अक्षम करने से कोड में बहुत अधिक परिवर्तन की आवश्यकता नहीं होती है:

Promise.resolve()
.then(function() {
    var xhr = new XMLHttpRequest();
    xhr.open('GET', 'https://stackoverflow.com/doesnotexist');
    return xhr;
})
.then(function(xhr) { return promiseResponse(xhr, false); })
.then(function(request) {
    console.log('Done');
    console.log(request.status + ' ' + request.statusText);
});

हमारा कॉलिंग कोड लंबा है, लेकिन वैचारिक रूप से, यह समझना अभी भी सरल है कि क्या हो रहा है। और हमें केवल इसकी सुविधाओं का समर्थन करने के लिए पूरे वेब अनुरोध एपीआई का पुनर्निर्माण नहीं करना है।

हम अपने कोड को साफ करने के लिए कुछ सुविधा फ़ंक्शंस जोड़ सकते हैं, साथ ही:

function makeSimpleGet(url) {
    var xhr = new XMLHttpRequest();
    xhr.open('GET', url);
    return xhr;
}

function promiseResponseAnyCode(xhr) {
    return promiseResponse(xhr, false);
}

फिर हमारा कोड बन जाता है:

Promise.resolve(makeSimpleGet('https://stackoverflow.com/doesnotexist'))
.then(promiseResponseAnyCode)
.then(function(request) {
    console.log('Done');
    console.log(request.status + ' ' + request.statusText);
});

5

jpmc26 का जवाब मेरी राय में एकदम सही है। इसकी कुछ कमियां हैं, हालांकि:

  1. यह अंतिम क्षण तक केवल xhr अनुरोध को उजागर करता है। यह POST-requests अनुरोध बॉडी को सेट करने की अनुमति नहीं देता है।
  2. यह पढ़ना कठिन है क्योंकि महत्वपूर्ण- sendफ़ंक्शन किसी फ़ंक्शन के अंदर छिपा हुआ है।
  3. यह वास्तव में अनुरोध करने पर बॉयलरप्लेट का काफी थोड़ा परिचय देता है।

बंदर xhr- वस्तु पैचिंग इन मुद्दों से निपटने:

function promisify(xhr, failNon2xx=true) {
    const oldSend = xhr.send;
    xhr.send = function() {
        const xhrArguments = arguments;
        return new Promise(function (resolve, reject) {
            // Note that when we call reject, we pass an object
            // with the request as a property. This makes it easy for
            // catch blocks to distinguish errors arising here
            // from errors arising elsewhere. Suggestions on a 
            // cleaner way to allow that are welcome.
            xhr.onload = function () {
                if (failNon2xx && (xhr.status < 200 || xhr.status >= 300)) {
                    reject({request: xhr});
                } else {
                    resolve(xhr);
                }
            };
            xhr.onerror = function () {
                reject({request: xhr});
            };
            oldSend.apply(xhr, xhrArguments);
        });
    }
}

अब उपयोग के रूप में सरल है:

let xhr = new XMLHttpRequest()
promisify(xhr);
xhr.open('POST', 'url')
xhr.setRequestHeader('Some-Header', 'Some-Value')

xhr.send(resource).
    then(() => alert('All done.'),
         () => alert('An error occured.'));

बेशक, यह एक अलग कमी पेश करता है: बंदर-पैचिंग प्रदर्शन को नुकसान पहुंचाता है। हालाँकि, यह मानने में कोई समस्या नहीं होनी चाहिए कि उपयोगकर्ता मुख्य रूप से xhr के परिणाम की प्रतीक्षा कर रहा है, कि अनुरोध स्वयं को कॉल सेट करने से अधिक परिमाण के आदेश लेता है और xhr अनुरोधों को अक्सर नहीं भेजा जा रहा है।

पुनश्च: और निश्चित रूप से आधुनिक ब्राउज़रों को लक्षित करने के लिए, भ्रूण का उपयोग करें!

पीपीएस: टिप्पणियों में बताया गया है कि यह विधि मानक एपीआई को बदलती है जो भ्रमित हो सकती है। बेहतर स्पष्टता के लिए xhr ऑब्जेक्ट पर एक अलग विधि पैच कर सकता है sendAndGetPromise()


मैं बन्दर पेटिंग से बचता हूँ क्योंकि यह आश्चर्य की बात है। अधिकांश डेवलपर्स उम्मीद करते हैं कि मानक एपीआई फ़ंक्शन नाम मानक एपीआई फ़ंक्शन का आह्वान करते हैं। यह कोड अभी भी वास्तविक sendकॉल को छुपाता है, लेकिन उन पाठकों को भी भ्रमित कर सकता है जो जानते हैं कि sendकोई वापसी मूल्य नहीं है। अधिक स्पष्ट कॉल का उपयोग करने से यह स्पष्ट हो जाता है कि अतिरिक्त तर्क को लागू किया गया है। मेरे उत्तर को तर्क को संभालने के लिए समायोजित करने की आवश्यकता है send; हालाँकि, fetchअब इसका उपयोग करना बेहतर होगा ।
jpmc26

मुझे लगता है कि यह निर्भर करता है। यदि आप xhr अनुरोध (जो किसी भी तरह संदिग्ध लगता है) को वापस / उजागर करते हैं तो आप बिल्कुल सही हैं। हालाँकि मैं यह नहीं देखता कि कोई ऐसा मॉड्यूल में क्यों नहीं करेगा और केवल परिणामी वादों को उजागर करेगा।
t.animal

मैं विशेष रूप से किसी को भी आपके द्वारा किए गए कोड को बनाए रखने का उल्लेख कर रहा हूं।
jpmc26

जैसा मैंने कहा: यह निर्भर करता है। यदि आपका मॉड्यूल इतना विशाल है कि प्रोमिसिफाई फ़ंक्शन बाकी कोड के बीच खो जाता है तो आपको शायद अन्य समस्याएं मिल गई हैं। यदि आपको एक मॉड्यूल मिला है, जहां आप बस कुछ एंडपॉइंट्स को कॉल करना चाहते हैं और वादे वापस करते हैं तो मुझे कोई समस्या नहीं दिखती है।
t.animal

मैं असहमत हूं कि यह आपके कोड आधार के आकार पर निर्भर करता है। यह मानक API फ़ंक्शन को मानक व्यवहार के अलावा कुछ और देखने के लिए भ्रमित करता है।
jpmc26
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.