我想在我的前端应用程序中使用(本机)promises来执行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);
});
答案 0 :(得分:317)
我假设您知道如何制作原生XHR请求(您可以刷新here和here)
由于any browser that supports native promises也支持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数据),但足以让我们前进。
我们可以这样构建一个承诺:
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参数和自定义标题。让我们使用选项对象而不是多个参数,使用签名:
{
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找到更全面的方法。
答案 1 :(得分:46)
这可以像下面的代码一样简单。
请注意,此代码只会在调用reject
时触发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
});
答案 2 :(得分:9)
答案 3 :(得分:7)
我认为我们可以通过不创建XMLHttpRequest
对象来使the top answer更加灵活和可重用。这样做的唯一好处是我们不必自己编写2或3行代码来完成它,并且它具有剥夺我们访问许多API功能的巨大缺点,比如设置标题。它还从应该处理响应的代码中隐藏原始对象的属性(对于成功和错误)。因此,只需将XMLHttpRequest
对象作为输入并将其作为结果传递,我们就可以制作更灵活,更广泛适用的功能。
此函数将任意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
s链,而不会牺牲XMLHttpRequest
API的灵活性:
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);
});
答案 4 :(得分:5)
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!
PPS:在评论中已指出,此方法更改了可能令人困惑的标准API。为了更好的说明,可以在xhr对象sendAndGetPromise()
上修补另一种方法。
答案 5 :(得分:0)
如果您希望代码在旧的浏览器中运行,请将其放在HTML文档的
中:<script>
self.Promise||document.write("<script src=/path/to/promise/polyfill.js><\/script>");
</script>
将/path/to/promise/polyfill.js替换为Promise polyfill的路径。如果该类不是本机类,则将创建一个Promise类,并允许您的代码在旧的浏览器(例如Internet Explorer)上运行。 Internet Explorer和其他旧版浏览器只占很小的市场份额,这看似微不足道,但这仍然可以转化为数百万的用户,因此,我不建议您完全撤消这些用户。
我可以建议这个Promise polyfill:
https://github.com/stefanpenner/es6-promise/
现在您可以访问Promise类。
如果您希望代码在IE 6-8等真正的旧浏览器中运行,则需要使用onreadystatechange而不是onload。这没有什么坏处,因为onreadystatechange仍在所有当前浏览器中使用,以实现向后兼容:
function send_request(xhr, data, timeout) {
return new Promise(function (resolve, reject) {
var s, p, i;
if (data && data.constructor==Object) {// serialize object
s = "_="+(new Date).getTime();
for (p in data) if (data.hasOwnProperty(p)) {
if (!data[p] || data[p].constructor!=Array) {
data[p] = [data[p]]
}
for (i=0; i<data[p].length; i++) {
s+= "&"+encodeuricomponent(p)+"="+encodeuricomponent(data[p][i]);
}
}
data = s;
}
xhr.onreadystatechange = function() {
if (xhr.readyState==4) {
resolve(xhr);
}
}
xhr.send(data);
if (timeout) {
settimeout(function() {
reject("timeout");
xhr.abort();
}, timeout);// milliseconds until timeout
}
});
}
xhr = new XMLHttpRequest();
xhr.open("GET", "/some/file", true);
send_request(xhr).then(function(xhr) {
if (xhr.status>=200 || xhr.status<400) {
//success
alert(xhr.responseText);
}
else {
return Promise.reject(xhr.statusText? xhr.status+" "+xhr.statusText: "error");
}
})
请记住,IE 6不支持XMLHttpRequest,因此您还需要同时填充ActiveX。文档
中的以下内容可能会起作用:<!--[if lt IE 7]>
<script>
// This is just an example. Use at your own risk.
function XMLHttpRequest() {
try {
return new ActiveXObject("Msxml2.XMLHTTP.6.0")
}
catch (e) {
return new ActiveXObject("Msxml2.XMLHTTP.3.0")
}
}
</script>
<![endif]-->