web-dev-qa-db-ja.com

ネイティブ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);
});
162
SomeKittens

私はあなたがネイティブXHR要求をする方法を知っていると仮定しています(あなたはブラッシュアップすることができます こちら そして こちら

ネイティブな約束をサポートするブラウザであればxhr.onloadもサポートするので、すべてのonReadyStateChange tomfooleryをスキップすることができます。一歩戻り、コールバックを使った基本的な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 dataのような)それほど複雑なことは含まれませんが、先に進むには十分です。

プロミスコンストラクタ

次のように約束を立てることができます。

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

Promiseコンストラクタは2つの引数が渡される関数を取ります(それらをresolverejectと呼びましょう)。これらはコールバックと考えることができます。1つは成功用、もう1つは失敗用です。例は素晴らしいです、このコンストラクタで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 にあります。

あるいは、 フェッチAPIpolyfill )を使用することもできます。

321
SomeKittens

これは、次のコードのように単純なものです。

このコードは、rejectが呼び出された場合(network errorsのみ)で、onerrorコールバックのみを起動し、HTTPステータスコードがエラーを示す場合は起動しないことに注意してください。これは他のすべての例外も除外します。それらを扱うことはあなた次第です、IMO。

また、イベント自体ではなく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
    });
46
Peleg

今これを検索する人なら誰でも フェッチ 関数を使うことができます。それはいくつかのかなり良いです サポート

私は最初に@ SomeKittensの答えを使用しましたが、それからfetchを発見しました。

9
microo8

XMLHttpRequestオブジェクトを作成しないようにすることで、 一番上の答え はるかに柔軟で再利用可能にできると思います。そうすることの唯一の利点は、私たちがそれをするために自分自身で2または3行のコードを書く必要がないということです、そしてそれはヘッダーの設定のようなAPIの機能の多くへのアクセスを奪うという大きな欠点があります。また、応答を処理することになっているコードから元のオブジェクトのプロパティを隠します(成功とエラーの両方に対して)。そのため、XMLHttpRequestオブジェクトをinputとして受け入れ、それをresultとして渡すことで、より柔軟でより広く適用可能な関数を作成できます。

この関数は任意のXMLHttpRequestオブジェクトをpromiseに変換し、デフォルトで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 APIの柔軟性を犠牲にすることなく、非常に自然にXMLHttpRequestsのチェーンに収まります。

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

呼び出しコードはもっと長いですが、概念的には、何が起こっているのか理解するのは簡単です。そして、その機能をサポートするためだけにWebリクエストAPI全体を再構築する必要はありません。

コードを整理するために、便利な関数をいくつか追加することもできます。

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

私の意見では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要求が頻繁に送信されないと仮定しても問題にならない。

シモンズ:そしてもちろん現代のブラウザをターゲットにしているなら、フェッチを使ってください!

PPS:このメソッドは標準的なAPIを変更するので、混乱を招く可能性があることがコメントで指摘されています。より明確にするために、xhrオブジェクトsendAndGetPromise()に異なるメソッドをパッチすることができます。

5
t.animal