Как я могу обещать родной XHR?
Я хочу использовать (родные) обещания в моем интерфейсном приложении для выполнения запроса 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);
});
5 ответов:
Я предполагаю, что вы знаете, как сделать собственный запрос XHR (вы можете освежить здесь и здесь)
С любой браузер, который поддерживает родной обещания также , мы можем пропустить все
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 });
конструктор promise принимает функцию, которая будет передана два аргумента (назовем их
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, так и пользовательские заголовки. Давайте использовать объект options вместо нескольких аргументов, с подписью:
{ 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.
в качестве альтернативы, вы мог бы использовать fetch API (полифилл).
это может быть так же просто, как следующий код.
имейте в виду, что этот код будет только огонь
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 });
для тех, кто ищет сейчас, вы можете использовать fetch. Он имеет некоторые довольно хорошие поддержка.
Я сначала использовал ответ @SomeKittens, но затем обнаружил
fetch
Что делает это для меня из коробки :)
я думаю, что мы можем сделать лучшие ответы гораздо более гибкий и многоразовый, не имея его создать
ответ jpmc26 довольно близок к идеальному, на мой взгляд. Однако у него есть некоторые недостатки:
- он предоставляет запрос xhr только до последнего момента. Это не позволяет
POST
- запросы для установки тела запроса.- это труднее читать как решающее
send
-звонок скрыт внутри функции.- он вводит довольно много шаблонных при фактическом оформлении запроса.
обезьяна латая 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 не отправляются часто.
PS: и, конечно, если вы нацелены на современные браузеры, используйте fetch!