Как я могу обещать родной 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 141

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 довольно близок к идеальному, на мой взгляд. Однако у него есть некоторые недостатки:

  1. он предоставляет запрос xhr только до последнего момента. Это не позволяет POST - запросы для установки тела запроса.
  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 не отправляются часто.

PS: и, конечно, если вы нацелены на современные браузеры, используйте fetch!