Javascript 我如何承诺原生 XHR?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/30008114/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How do I promisify native XHR?
提问by SomeKittens
I want to use (native) promises in my frontend app to perform XHR request but without all the tomfoolery of a massive framework.
我想在我的前端应用程序中使用(本机)promise 来执行 XHR 请求,但没有大型框架的所有愚蠢行为。
I want my xhr to return a promise but this doesn't work (giving me: Uncaught TypeError: Promise resolver undefined is not a function)
我希望我的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);
});
回答by SomeKittens
I'm assuming you know how to make a native XHR request (you can brush up hereand here)
我假设您知道如何发出本机 XHR 请求(您可以在这里和这里复习)
Since any browser that supports native promiseswill also support xhr.onload, we can skip all the onReadyStateChangetomfoolery. Let's take a step back and start with a basic XHR request function using callbacks:
由于任何支持原生 promises 的浏览器也将支持xhr.onload,我们可以跳过所有的onReadyStateChangetomfoolery。让我们退后一步,从使用回调的基本 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);
});
Hurrah! This doesn't involve anything terribly complicated (like custom headers or POST data) but is enough to get us moving forwards.
欢呼!这不涉及任何非常复杂的事情(如自定义标头或 POST 数据),但足以让我们继续前进。
The promise constructor
承诺构造函数
We can construct a promise like so:
我们可以像这样构造一个 promise:
new Promise(function (resolve, reject) {
// Do some Async stuff
// call resolve if it succeeded
// reject if it failed
});
The promise constructor takes a function that will be passed two arguments (let's call them resolveand reject). You can think of these as callbacks, one for success and one for failure. Examples are awesome, let's update makeRequestwith this constructor:
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);
});
Now we can tap into the power of promises, chaining multiple XHR calls (and the .catchwill trigger for an error on either call):
现在我们可以利用 promises 的力量,链接多个 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);
});
We can improve this still further, adding both POST/PUT params and custom headers. Let's use an options object instead of multiple arguments, with the signature:
我们可以进一步改进这一点,添加 POST/PUT 参数和自定义标头。让我们使用带有签名的选项对象而不是多个参数:
{
method: String,
url: String,
params: String | Object,
headers: Object
}
makeRequestnow looks something like this:
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);
});
A more comprehensive approach can be found at MDN.
可以在MDN找到更全面的方法。
回答by Peleg
This could be as simple as the following code.
这可能像以下代码一样简单。
Keep in mind that this code will only fire the rejectcallback when onerroris called (networkerrors only) and not when the HTTP status code signifies an error. This will also exclude all other exceptions. Handling those should be up to you, IMO.
请记住,此代码只会reject在onerror被调用时(仅限网络错误)触发回调,而不会在 HTTP 状态代码表示错误时触发。这也将排除所有其他例外。处理这些应该取决于你,IMO。
Additionally, it is recommended to call the rejectcallback with an instance of Errorand not the event itself, but for sake of simplicity, I left as is.
此外,建议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();
});
}
And invoking it could be this:
调用它可能是这样的:
request('GET', 'http://google.com')
.then(function (e) {
console.log(e.target.response);
}, function (e) {
// handle errors
});
回答by microo8
回答by jpmc26
I think we can make the top answermuch more flexible and reusable by not having it create the XMLHttpRequestobject. The only benefit of doing so is that we don't have to write 2 or 3 lines of code ourselves to do it, and it has the enormous drawback of taking away our access to many of the API's features, like setting headers. It also hides properties of the original object from the code that's supposed to handle the response (for both successes and errors). So we can make a more flexible, more widely applicable function by just accepting the XMLHttpRequestobject as inputand passing it as the result.
我认为我们可以通过不让它创建对象来使最佳答案更加灵活和可重用XMLHttpRequest。这样做的唯一好处是我们不必自己编写 2 或 3 行代码来执行此操作,而且它的巨大缺点是剥夺了我们对许多 API 功能(例如设置标头)的访问权。它还从应该处理响应(成功和错误)的代码中隐藏原始对象的属性。所以我们可以通过只接受XMLHttpRequest对象作为输入并将其作为结果传递来制作一个更灵活、更广泛适用的函数。
This function converts an arbitrary XMLHttpRequestobject into a promise, treating non-200 status codes as an error by default:
此函数将任意XMLHttpRequest对象转换为承诺,默认情况下将非 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();
});
}
This function fits very naturally into a chain of Promises, without sacrificing the flexibility of the XMLHttpRequestAPI:
这个函数非常自然地适合一个Promises链,而不会牺牲XMLHttpRequestAPI的灵活性:
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);
});
catchwas omitted above to keep the sample code simpler. You should always have one, and of course we can:
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);
}
});
And disabling the HTTP status code handling doesn't require much change in the code:
禁用 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);
});
Our calling code is longer, but conceptually, it's still simple to understand what's going on. And we don't have to rebuild the entire web request API just to support its features.
我们的调用代码更长,但从概念上讲,理解发生了什么仍然很简单。而且我们不必为了支持其功能而重新构建整个 Web 请求 API。
We can add a few convenience functions to tidy up our code, as well:
我们也可以添加一些方便的函数来整理我们的代码:
function makeSimpleGet(url) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
return xhr;
}
function promiseResponseAnyCode(xhr) {
return promiseResponse(xhr, false);
}
Then our code becomes:
那么我们的代码就变成了:
Promise.resolve(makeSimpleGet('https://stackoverflow.com/doesnotexist'))
.then(promiseResponseAnyCode)
.then(function(request) {
console.log('Done');
console.log(request.status + ' ' + request.statusText);
});
回答by t.animal
jpmc26's answer is quite close to perfect in my opinion. It has some drawbacks, though:
在我看来,jpmc26 的答案非常接近完美。不过它也有一些缺点:
- It exposes the xhr request only until the last moment. This does not allow
POST-requests to set the request body. - It is harder to read as the crucial
send-call is hidden inside a function. - It introduces quite a bit of boilerplate when actually making the request.
- 它只在最后一刻公开 xhr 请求。这不允许
POST-requests 设置请求正文。 - 由于关键的
send-call 隐藏在函数中,因此更难阅读。 - 它在实际发出请求时引入了相当多的样板文件。
Monkey patching the xhr-object tackles these issues:
猴子修补 xhr-object 解决了这些问题:
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);
});
}
}
Now the usage is as simple as:
现在的用法很简单:
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.'));
Of course, this introduces a different drawback: Monkey-patching does hurt performance. However this should not be a problem assuming that the user is waiting mainly for the result of the xhr, that the request itself takes orders of magnitude longer than setting up the call and xhr requests not being sent frequently.
当然,这引入了一个不同的缺点:Monkey-patching 确实会影响性能。然而,假设用户主要等待 xhr 的结果,那么这应该不是问题,请求本身比设置呼叫花费的时间长几个数量级,并且 xhr 请求不经常发送。
PS: And of course if targeting modern browsers, use fetch!
PS:当然,如果针对现代浏览器,请使用 fetch!
PPS: It has been pointed out in the comments that this method changes the standard API which can be confusing. For better clarity one could patch a different method onto the xhr object sendAndGetPromise().
PPS:评论中已指出此方法更改了标准 API,这可能会令人困惑。为了更清晰,可以将不同的方法修补到 xhr 对象上sendAndGetPromise()。

