私はフロントエンドのアプリで(ネイティブの)約束を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);
});
私はあなたがネイティブ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つの引数が渡される関数を取ります(それらをresolve
とreject
と呼びましょう)。これらはコールバックと考えることができます。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 にあります。
これは、次のコードのように単純なものです。
このコードは、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
});
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の柔軟性を犠牲にすることなく、非常に自然にXMLHttpRequest
sのチェーンに収まります。
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);
});
私の意見ではjpmc26の答えは完璧に近いです。ただし、いくつかの欠点があります。
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要求が頻繁に送信されないと仮定しても問題にならない。
シモンズ:そしてもちろん現代のブラウザをターゲットにしているなら、フェッチを使ってください!
PPS:このメソッドは標準的なAPIを変更するので、混乱を招く可能性があることがコメントで指摘されています。より明確にするために、xhrオブジェクトsendAndGetPromise()
に異なるメソッドをパッチすることができます。