Come posso promettere XHR nativo?

Voglio usare promesse (native) nella mia app di frontend per eseguire la richiesta XHR ma senza tutte le buffonate di un enorme framework.

Voglio che il mio xhr restituisca una promise ma questo non funziona (dandomi: 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); }); 

Suppongo che tu sappia come fare una richiesta XHR nativa (puoi rispolverare qui e qui )

Dal momento che qualsiasi browser che supporta le promesse native supporterà anche xhr.onload , possiamo saltare tutta la tomfoolery onReadyStateChange . Facciamo un passo indietro e iniziamo con una funzione di richiesta XHR di base usando i callback:

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

Evviva! Questo non implica nulla di terribilmente complicato (come intestazioni personalizzate o dati POST) ma è sufficiente per farci andare avanti.

Il costruttore di promise

Possiamo build una promise in questo modo:

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

Il costruttore di promesse accetta una funzione che riceverà due argomenti (chiamiamoli resolve e reject ). Puoi pensare a questi come callback, uno per il successo e uno per il fallimento. Gli esempi sono fantastici, aggiorna makeRequest con questo costruttore:

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

Ora possiamo attingere al potere delle promesse, concatenando più chiamate XHR (e il .catch si innesca per un errore in entrambe le chiamate):

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

Possiamo migliorare ulteriormente, aggiungendo i parametri POST / PUT e le intestazioni personalizzate. Usiamo un object opzioni invece di più argomenti, con la firma:

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

makeRequest ora assomiglia a qualcosa del genere:

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

Un approccio più completo può essere trovato in MDN .

In alternativa, è ansible utilizzare l' API di recupero ( polyfill ).

Questo potrebbe essere semplice come il seguente codice.

Tieni presente che questo codice onerror richiamata di reject solo quando viene chiamato onerror (solo errori di rete ) e non quando il codice di stato HTTP indica un errore. Questo escluderà anche tutte le altre eccezioni. Gestire quelli dovrebbero essere a voi, IMO.

Inoltre, si consiglia di chiamare la richiamata di reject con un’istanza di Error e non l’evento stesso, ma per ragioni di semplicità, ho lasciato così com’è.

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

E invocarlo potrebbe essere questo:

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

Per chiunque lo cerchi ora, puoi utilizzare la funzione di recupero . Ha un supporto piuttosto buono.

Ho usato per la prima volta la risposta di @ SomeKittens, ma poi ho scoperto che fetch lo fa fuori dalla scatola 🙂

Penso che possiamo rendere la risposta migliore molto più flessibile e riusabile non avendo creato l’object XMLHttpRequest . L’unico vantaggio di farlo è che non dobbiamo scrivere noi stessi 2 o 3 linee di codice per farlo, e ha l’enorme svantaggio di togliere il nostro accesso a molte delle funzionalità dell’API, come l’impostazione delle intestazioni. Nasconde anche le proprietà dell’object originale dal codice che dovrebbe gestire la risposta (sia per i successi che per gli errori). Quindi possiamo rendere una funzione più flessibile e più ampiamente applicabile semplicemente accettando l’object XMLHttpRequest come input e passandolo come risultato .

Questa funzione converte un object XMLHttpRequest arbitrario in una promise, trattando i codici di stato non 200 come un errore per impostazione predefinita:

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

Questa funzione si adatta molto naturalmente a una catena di Promise s, senza sacrificare la flessibilità dell’API 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 stato omesso sopra per mantenere il codice di esempio più semplice. Dovresti sempre averne uno e, naturalmente, possiamo:

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

E disabilitare la gestione del codice di stato HTTP non richiede molte modifiche nel codice:

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

Il nostro codice di chiamata è più lungo, ma concettualmente, è ancora semplice capire cosa sta succedendo. E non dobbiamo ribuild l’intera API di richiesta web solo per supportare le sue funzionalità.

Possiamo aggiungere alcune funzioni utili per riordinare il nostro codice, inoltre:

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

Quindi il nostro codice diventa:

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

La risposta di jpmc26 è abbastanza vicina alla perfezione secondo me. Ha alcuni inconvenienti, però:

  1. Espone la richiesta xhr solo fino all’ultimo momento. Ciò non consente alle richieste POST di impostare il corpo della richiesta.
  2. È più difficile da leggere poiché la chiamata di send cruciale è nascosta all’interno di una funzione.
  3. Presenta un bel po ‘di testo quando si effettua la richiesta.

Monkey patching the xhr-object affronta questi problemi:

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

Ora l’utilizzo è semplice come:

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

Naturalmente, questo introduce un altro inconveniente: il patch delle scimmie danneggia le prestazioni. Tuttavia, ciò non dovrebbe costituire un problema presumendo che l’utente stia aspettando principalmente il risultato della xhr, che la richiesta stessa richieda ordini di grandezza più lunghi rispetto all’impostazione della chiamata e che le richieste xhr non vengano inviate frequentemente.

PS: E, naturalmente, se scegli come target i browser moderni, usa fetch!