Yerli XHR'ye nasıl söz verebilirim?


183

XHR isteğini gerçekleştirmek için ön uç uygulamamda (yerel) vaatler kullanmak istiyorum, ancak büyük bir çerçevenin tüm tomfooleri olmadan.

Benim xhr söz dönmek istiyorum ama bu işe yaramazsa (me vererek: 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);
});

Yanıtlar:


369

Yerel bir XHR isteğini nasıl yapacağınızı bildiğinizi varsayıyorum ( burada ve burada fırçalayabilirsiniz )

Yana destekleri yerli sözler o herhangi bir tarayıcı da destekleyecek xhr.onload, hepimiz atlayabilirsiniz onReadyStateChangemaskaralık. Bir adım geri gidelim ve geri çağrıları kullanarak temel bir XHR istek işleviyle başlayalım:

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

Yaşa! Bu çok karmaşık bir şey içermez (özel başlıklar veya POST verileri gibi) ancak bizi ileriye taşımak için yeterlidir.

Söz Verici

Biz böyle bir söz inşa edebiliriz:

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

Promise yapıcısı iki argümandan geçecek bir fonksiyon alır (diyelim ki onları resolveve reject). Bunları biri başarı, diğeri başarısız olmak için geri aramalar olarak düşünebilirsiniz. Örnekler harika, makeRequestbu kurucu ile güncelleyelim :

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

Şimdi, birden fazla XHR çağrısını zincirleyerek sözlerin gücünden faydalanabiliriz (ve .catchher iki çağrıda da bir hata için tetikleyeceğiz):

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

Hem POST / PUT parametrelerini hem de özel başlıkları ekleyerek bunu daha da geliştirebiliriz. Şimdi imzayla birden fazla argüman yerine bir seçenekler nesnesi kullanalım:

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

makeRequest şimdi şuna benzer:

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

Daha kapsamlı bir yaklaşım bulunabilir MDN'yi .

Alternatif olarak, getirme API'sını ( çoklu doldurma ) kullanabilirsiniz.


3
responseTypeKimlik doğrulama, kimlik bilgileri, vb timeout. İçin seçenekler de eklemek isteyebilirsiniz. Ve paramsnesneler blob / bufferviews ve FormDataörneklerini desteklemelidir
Bergi

4
Reddedildiğinde yeni bir Hata döndürmek daha iyi olur mu?
prasanthv

1
Ayrıca, bu durumda boş oldukları için geri dönüş xhr.statusve xhr.statusTexthata yapmak mantıklı değildir .
dqd

2
Bu kod, bir şey hariç, reklamı yapılan gibi görünüyor. Parametleri bir GET isteğine iletmenin doğru yolunun xhr.send (params) yoluyla olmasını bekledim. Ancak, GET istekleri send () yöntemine gönderilen değerleri yok sayar. Bunun yerine, URL'nin kendisinde yalnızca sorgu dizesi parametreleri olmalıdır. Bu nedenle, yukarıdaki yöntem için, "params" bağımsız değişkeninin bir GET isteğine uygulanmasını istiyorsanız, yordamın bir GET ile POST'u tanıyacak şekilde değiştirilmesi ve daha sonra bu değerleri xhr'a teslim edilen URL'ye koşullu olarak eklemesi gerekir. .açık().
Ekim'te hairbo

1
Biri kullanmak gerekir resolve(xhr.response | xhr.responseText);Çoğu tarayıcıda repsonse responseText bu arada.
heinob

50

Bu, aşağıdaki kod kadar basit olabilir.

Bu durumun, HTTP durum kodu bir hata belirttiği zaman değil, yalnızca rejectgeri onerrorçağrıldığında ( yalnızca hataları) geri çağrıyı tetikleyeceğini unutmayın. Bu, diğer tüm istisnaları da hariç tutacaktır. Bunları ele almak sana kalmış olmalı, IMO.

Ayrıca, rejectgeri çağrıyı Errorolayın kendisinin değil, bir örneği ile çağırmanız önerilir , ancak basitlik için olduğu gibi bıraktım.

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

Ve çağırmak şu olabilir:

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

14
@MadaraUchiha Sanırım bu tl; dr versiyonu. OP'ye sorularına bir cevap verir ve sadece bu.
Peleg

POST isteği nereden gidiyor?
caub

1
@crl normal bir XHR'de olduğu gibi:xhr.send(requestBody)
Peleg

evet ama kodunuzda buna neden izin vermediniz? (yöntemi parametrelendirdiğiniz için)
caub

6
Soruyu cevaplayan hemen çalışmak için çok basit bir kod sağladığı için bu cevabı beğendim.
Steve Chamaillard

12

Bunu şimdi arayan herkes için getir işlevini kullanabilirsiniz . Oldukça iyi bir desteği var .

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

İlk önce @ SomeKittens'in cevabını kullandım, ama fetchbunun benim için kutudan çıktığını fark ettim :)


2
Eski tarayıcılar fetchişlevi desteklemez , ancak GitHub bir çoklu dolgu yayınladı .
bdesham

1
fetchHenüz iptal işlemini desteklemediğinden tavsiye etmem .
James Dunne


Stackoverflow.com/questions/31061838/… adresindeki yanıt, şimdiye kadar Firefox 57+ ve Edge 16+'da çalışan iptal edilebilir kod örneğine sahiptir
sideshowbarker

1
@ microo8 Getirmeyi kullanarak basit bir örneğe sahip olmak güzel olurdu ve burada koymak için iyi bir yer gibi görünüyor.
jpaugh

8

En iyi cevabı , XMLHttpRequestnesneyi yaratmayarak çok daha esnek ve tekrar kullanılabilir hale getirebileceğimizi düşünüyorum . Bunu yapmanın tek yararı, bunu yapmak için kendimize 2 veya 3 satır kod yazmak zorunda olmamamızdır ve üstbilgileri ayarlamak gibi API özelliklerinin çoğuna erişimimizi ortadan kaldırmanın muazzam dezavantajına sahiptir. Ayrıca, orijinal nesnenin özelliklerini yanıtı işlemesi gereken koddan (hem başarılar hem de hatalar için) gizler. Böylece, XMLHttpRequestnesneyi yalnızca girdi olarak kabul edip sonuç olarak ileterek daha esnek, daha yaygın olarak uygulanabilir bir işlev yapabiliriz .

Bu işlev, rastgele XMLHttpRequestolmayan bir nesneyi varsayılan olmayan bir duruma 200 olmayan durum kodları gibi davranarak bir vaat haline getirir:

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

Bu işlev Promise, XMLHttpRequestAPI'nın esnekliğinden ödün vermeden çok doğal bir s zincirine uyar :

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örnek kodunu daha basit tutmak için yukarıda atlanmıştır. Her zaman bir tane olmalı ve tabii ki şunları yapabiliriz:

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 durum kodu işlemeyi devre dışı bırakmak, kodda çok fazla değişiklik yapılmasını gerektirmez:

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

Arama kodumuz daha uzun, ancak kavramsal olarak, neler olup bittiğini anlamak hala basit. Ayrıca, yalnızca özelliklerini desteklemek için web isteği API'sının tamamını yeniden yapılandırmamız gerekmez.

Kodumuzu düzenlemek için birkaç kolaylık işlevi de ekleyebiliriz:

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

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

Sonra kodumuz:

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

5

Benim görüşüme göre jpmc26 cevap mükemmel yakındır. Yine de bazı dezavantajları var:

  1. Xhr isteğini yalnızca son ana kadar gösterir. Bu, POST-isteklerinin istek gövdesini ayarlamasına izin vermez .
  2. Önemli send-arama bir işlev içinde gizlendiğinden okumak daha zordur .
  3. Gerçekte istekte bulunurken oldukça küçük bir isim levhası sunar.

Xhr nesnesini yamalayan maymun şu sorunları ele alır:

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

Şimdi kullanım şu kadar basit:

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

Tabii ki, bu farklı bir dezavantaj getiriyor: Maymun yama, performansa zarar veriyor. Ancak bu, kullanıcının esas olarak xhr sonucunu beklediğini, isteğin kendisinin çağrıyı ayarlamaktan daha fazla büyüklük aldığı ve xhr isteklerinin sık gönderilmediği varsayımıyla bir sorun olmamalıdır.

PS: Ve tabii ki modern tarayıcıları hedefliyorsanız, getir!

PPS: Yorumlarda, bu yöntemin kafa karıştırıcı olabilecek standart API'yi değiştirdiği belirtildi. Daha iyi anlaşılırlık için xhr nesnesine farklı bir yöntem eklenebilir sendAndGetPromise().


Maymun yamalarından kaçınırım çünkü şaşırtıcı. Çoğu geliştirici, standart API işlev adlarının standart API işlevini çağırmasını bekler. Bu kod hala gerçek sendaramayı gizler, ancak senddönüş değeri olmadığını bilen okuyucuları da karıştırabilir . Daha açık çağrılar kullanmak, ek mantığın çağrıldığını daha net hale getirir. Cevabımın argümanları ele alacak şekilde ayarlanması gerekiyor send; ancak, fetchşimdi kullanmak muhtemelen daha iyidir .
jpmc26

Sanırım buna bağlı. Eğer iade / xhr isteği (ki yine de şüpheli görünüyor) ortaya kesinlikle haklısın. Ancak neden bir modül içinde bunu yapmak ve sadece elde edilen vaatleri açığa vurmak anlamıyorum.
t.animal

Ben özel olarak bunu yapmak kodu korumak zorunda herkese atıfta bulunuyorum.
jpmc26

Dediğim gibi: Değişir. Modülünüz o kadar büyükse, promisify işlevi kodun geri kalanı arasında kaybolursa, muhtemelen başka sorunlarınız vardır. Sadece bazı uç noktaları aramak ve sözleri geri vermek istediğiniz bir modülünüz varsa, bir sorun görmüyorum.
t.animal

Kod tabanınızın boyutuna bağlı olduğuna katılmıyorum. Standart bir API işlevinin standart davranıştan başka bir şey yaptığını görmek kafa karıştırıcıdır.
jpmc26
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.