Related
Want to improve this post? Provide detailed answers to this question, including citations and an explanation of why your answer is correct. Answers without enough detail may be edited or deleted.
I want to use (native) promises in my frontend app to perform XHR request but without all the tomfoolery of a massive framework.
I want my xhr to return a promise but this doesn't work (giving me: 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);
});
I'm assuming you know how to make a native XHR request (you can brush up here and here)
Since any browser that supports native promises will also support xhr.onload, we can skip all the onReadyStateChange tomfoolery. Let's take a step back and start with a basic XHR request function using callbacks:
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.
The promise constructor
We can construct a promise like so:
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 resolve and reject). You can think of these as callbacks, one for success and one for failure. Examples are awesome, let's update makeRequest with this constructor:
function makeRequest (method, url) {
return new Promise(function (resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.open(method, url);
xhr.onload = function () {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.response);
} else {
reject({
status: xhr.status,
statusText: xhr.statusText
});
}
};
xhr.onerror = function () {
reject({
status: xhr.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 .catch will trigger for an error on either call):
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:
{
method: String,
url: String,
params: String | Object,
headers: Object
}
makeRequest now looks something like this:
function makeRequest (opts) {
return new Promise(function (resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.open(opts.method, opts.url);
xhr.onload = function () {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.response);
} else {
reject({
status: xhr.status,
statusText: xhr.statusText
});
}
};
xhr.onerror = function () {
reject({
status: xhr.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.
Alternatively, you could use the fetch API (polyfill).
This could be as simple as the following code.
Keep in mind that this code will only fire the reject callback when onerror is called (network errors 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.
Additionally, it is recommended to call the reject callback with an instance of Error and not the event itself, but for sake of simplicity, I left as is.
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
});
For anyone who searches for this now, you can use the fetch function.
It has some pretty good support.
fetch('http://example.com/movies.json')
.then(response => response.json())
.then(data => console.log(data));
I've firstly used #SomeKittens's answer, but then discovered fetch that does it for me out of the box :)
I think we can make the top answer much more flexible and reusable by not having it create the XMLHttpRequest object. 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 XMLHttpRequest object as input and passing it as the result.
This function converts an arbitrary XMLHttpRequest object into a promise, treating non-200 status codes as an error by default:
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 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 was omitted above to keep the sample code simpler. You should always have one, and of course we can:
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:
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.
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);
});
jpmc26's answer is quite close to perfect in my opinion. It has some drawbacks, though:
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.
Monkey patching the xhr-object tackles these issues:
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.
PS: And of course if targeting modern browsers, use 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().
If you want your code to work in old browsers, put this in the <head> of your HTML document:
<script>
self.Promise||document.write("<script src=/path/to/promise/polyfill.js><\/script>");
</script>
Replace /path/to/promise/polyfill.js with the path to your Promise polyfill. This will create a Promise class if the class is not already native and allow your code to run on old browsers such as Internet Explorer. Internet Explorer and other old browsers make up a small minority of the market share which might seem insignificant, but this still translates to millions of users so I do not recommend totally dismissing these users.
May I suggest this Promise polyfill:
https://github.com/stefanpenner/es6-promise/
Now you have access to the Promise class.
If you want your code to work in really old browsers like IE 6-8 you need to use onreadystatechange instead of onload. There is no harm in this as onreadystatechange remains in use in all current browsers for backwards compatibility:
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");
}
})
Keep in mind that IE 6 does not support XMLHttpRequest so you would need to polyfill that as well which you can do with ActiveX. Something like the following in your document <head> might work:
<!--[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]-->
Want to improve this post? Provide detailed answers to this question, including citations and an explanation of why your answer is correct. Answers without enough detail may be edited or deleted.
I want to use (native) promises in my frontend app to perform XHR request but without all the tomfoolery of a massive framework.
I want my xhr to return a promise but this doesn't work (giving me: 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);
});
I'm assuming you know how to make a native XHR request (you can brush up here and here)
Since any browser that supports native promises will also support xhr.onload, we can skip all the onReadyStateChange tomfoolery. Let's take a step back and start with a basic XHR request function using callbacks:
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.
The promise constructor
We can construct a promise like so:
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 resolve and reject). You can think of these as callbacks, one for success and one for failure. Examples are awesome, let's update makeRequest with this constructor:
function makeRequest (method, url) {
return new Promise(function (resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.open(method, url);
xhr.onload = function () {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.response);
} else {
reject({
status: xhr.status,
statusText: xhr.statusText
});
}
};
xhr.onerror = function () {
reject({
status: xhr.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 .catch will trigger for an error on either call):
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:
{
method: String,
url: String,
params: String | Object,
headers: Object
}
makeRequest now looks something like this:
function makeRequest (opts) {
return new Promise(function (resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.open(opts.method, opts.url);
xhr.onload = function () {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.response);
} else {
reject({
status: xhr.status,
statusText: xhr.statusText
});
}
};
xhr.onerror = function () {
reject({
status: xhr.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.
Alternatively, you could use the fetch API (polyfill).
This could be as simple as the following code.
Keep in mind that this code will only fire the reject callback when onerror is called (network errors 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.
Additionally, it is recommended to call the reject callback with an instance of Error and not the event itself, but for sake of simplicity, I left as is.
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
});
For anyone who searches for this now, you can use the fetch function.
It has some pretty good support.
fetch('http://example.com/movies.json')
.then(response => response.json())
.then(data => console.log(data));
I've firstly used #SomeKittens's answer, but then discovered fetch that does it for me out of the box :)
I think we can make the top answer much more flexible and reusable by not having it create the XMLHttpRequest object. 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 XMLHttpRequest object as input and passing it as the result.
This function converts an arbitrary XMLHttpRequest object into a promise, treating non-200 status codes as an error by default:
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 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 was omitted above to keep the sample code simpler. You should always have one, and of course we can:
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:
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.
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);
});
jpmc26's answer is quite close to perfect in my opinion. It has some drawbacks, though:
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.
Monkey patching the xhr-object tackles these issues:
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.
PS: And of course if targeting modern browsers, use 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().
If you want your code to work in old browsers, put this in the <head> of your HTML document:
<script>
self.Promise||document.write("<script src=/path/to/promise/polyfill.js><\/script>");
</script>
Replace /path/to/promise/polyfill.js with the path to your Promise polyfill. This will create a Promise class if the class is not already native and allow your code to run on old browsers such as Internet Explorer. Internet Explorer and other old browsers make up a small minority of the market share which might seem insignificant, but this still translates to millions of users so I do not recommend totally dismissing these users.
May I suggest this Promise polyfill:
https://github.com/stefanpenner/es6-promise/
Now you have access to the Promise class.
If you want your code to work in really old browsers like IE 6-8 you need to use onreadystatechange instead of onload. There is no harm in this as onreadystatechange remains in use in all current browsers for backwards compatibility:
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");
}
})
Keep in mind that IE 6 does not support XMLHttpRequest so you would need to polyfill that as well which you can do with ActiveX. Something like the following in your document <head> might work:
<!--[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]-->
I'm attempting to execute a CORS request, then use async/await to get value out of it (I'm using jquery btw).
The functions createCORSRequest and executeCORSRequest both work fine I believe, so the implementation doesn't really matter. The function getDailyGames uses await to receive a promise created from executeCORSRequest, and then returns that promise.
My trouble is with actually using the promise in the $(document).ready function (this is basically the main function in jquery).
In the commented out line, the '.then' the method is used on the result of getDailyGames to print out the result of the function (this works fine).
However, I want to store the value of this promise in the 'result' variable and do stuff to it, but I can't get it out of the promise.
How would I correctly return this promise so that I can store the resolved value in a variable and do stuff with it?
Or another way to ask this: why does the 'result' variable in getDailyGames get a promise? Shouldn't use await with a promise give you the resolved value directly?
$(document).ready(function() {
//getDailyGames(20190313).then(result => console.log(result));
let result = getDailyGames(20190313);
//DO STUFF WITH RESULT
});
async function getDailyGames(date){
const url = 'https://api.jjjacobson.com/dailygames?season=2018-2019-
regular&date=' + date;
let result = await executeCORSRequest(url);
return result;
}
function executeCORSRequest(url){
return new Promise(function(resolve, reject) {
const xhr = createCORSRequest('GET', url);
if (!xhr) {
throw new Error('CORS not supported');
}
xhr.onload = function() {
resolve(xhr.responseText);
};
xhr.onerror = function() {
console.log('There was an error!');
reject('ERROR');
};
xhr.send();
});
}
function createCORSRequest(method, url) {
let xhr = new XMLHttpRequest();
if ("withCredentials" in xhr) {
// Check if the XMLHttpRequest object has a "withCredentials" property.
// "withCredentials" only exists on XMLHTTPRequest2 objects.
xhr.open(method, url, true);
}
else if (typeof XDomainRequest != "undefined") {
// Otherwise, check if XDomainRequest.
// XDomainRequest only exists in IE, and is IE's way of making CORS requests.
xhr = new XDomainRequest();
xhr.open(method, url);
}
else {
// Otherwise, CORS is not supported by the browser.
xhr = null;
}
return xhr;
}
You should change your document.ready function to:
$(document).ready(function() {
let result;
getDailyGames(20190313).then(res => {
result = res;
//DO STUFF WITH RESULT
});
});
Await works only when the function itself is async. You could also do this
$(document).ready(async function() {
let result = await getDailyGames(20190313);
//DO STUFF WITH RESULT
});
Async/await is only syntactical sugar aiming at eliminating the callback hell. It should be remembered well that somewhere down the asynchronous chain, you will have to deal with a promise. This is the reason why you can use await only inside async function and, sadly, the root of the script is not an async function.
In your case, if you want to serialize the call to getDailyGames, make the document.ready an async function as indicated by #varun.
Want to improve this post? Provide detailed answers to this question, including citations and an explanation of why your answer is correct. Answers without enough detail may be edited or deleted.
I want to use (native) promises in my frontend app to perform XHR request but without all the tomfoolery of a massive framework.
I want my xhr to return a promise but this doesn't work (giving me: 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);
});
I'm assuming you know how to make a native XHR request (you can brush up here and here)
Since any browser that supports native promises will also support xhr.onload, we can skip all the onReadyStateChange tomfoolery. Let's take a step back and start with a basic XHR request function using callbacks:
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.
The promise constructor
We can construct a promise like so:
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 resolve and reject). You can think of these as callbacks, one for success and one for failure. Examples are awesome, let's update makeRequest with this constructor:
function makeRequest (method, url) {
return new Promise(function (resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.open(method, url);
xhr.onload = function () {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.response);
} else {
reject({
status: xhr.status,
statusText: xhr.statusText
});
}
};
xhr.onerror = function () {
reject({
status: xhr.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 .catch will trigger for an error on either call):
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:
{
method: String,
url: String,
params: String | Object,
headers: Object
}
makeRequest now looks something like this:
function makeRequest (opts) {
return new Promise(function (resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.open(opts.method, opts.url);
xhr.onload = function () {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.response);
} else {
reject({
status: xhr.status,
statusText: xhr.statusText
});
}
};
xhr.onerror = function () {
reject({
status: xhr.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.
Alternatively, you could use the fetch API (polyfill).
This could be as simple as the following code.
Keep in mind that this code will only fire the reject callback when onerror is called (network errors 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.
Additionally, it is recommended to call the reject callback with an instance of Error and not the event itself, but for sake of simplicity, I left as is.
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
});
For anyone who searches for this now, you can use the fetch function.
It has some pretty good support.
fetch('http://example.com/movies.json')
.then(response => response.json())
.then(data => console.log(data));
I've firstly used #SomeKittens's answer, but then discovered fetch that does it for me out of the box :)
I think we can make the top answer much more flexible and reusable by not having it create the XMLHttpRequest object. 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 XMLHttpRequest object as input and passing it as the result.
This function converts an arbitrary XMLHttpRequest object into a promise, treating non-200 status codes as an error by default:
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 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 was omitted above to keep the sample code simpler. You should always have one, and of course we can:
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:
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.
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);
});
jpmc26's answer is quite close to perfect in my opinion. It has some drawbacks, though:
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.
Monkey patching the xhr-object tackles these issues:
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.
PS: And of course if targeting modern browsers, use 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().
If you want your code to work in old browsers, put this in the <head> of your HTML document:
<script>
self.Promise||document.write("<script src=/path/to/promise/polyfill.js><\/script>");
</script>
Replace /path/to/promise/polyfill.js with the path to your Promise polyfill. This will create a Promise class if the class is not already native and allow your code to run on old browsers such as Internet Explorer. Internet Explorer and other old browsers make up a small minority of the market share which might seem insignificant, but this still translates to millions of users so I do not recommend totally dismissing these users.
May I suggest this Promise polyfill:
https://github.com/stefanpenner/es6-promise/
Now you have access to the Promise class.
If you want your code to work in really old browsers like IE 6-8 you need to use onreadystatechange instead of onload. There is no harm in this as onreadystatechange remains in use in all current browsers for backwards compatibility:
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");
}
})
Keep in mind that IE 6 does not support XMLHttpRequest so you would need to polyfill that as well which you can do with ActiveX. Something like the following in your document <head> might work:
<!--[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]-->
I'm new to JavaScript programming. I'm now working on my Google Chrome Extension. This is the code that doesn't work... :P
I want getURLInfo function to return its JSON object, and want to put it into resp. Could someone please fix my code to get it work?
function getURLInfo(url)
{
var xhr = new XMLHttpRequest();
xhr.open
(
"GET",
"http://RESTfulAPI/info.json?url="
+ escape(url),
true
);
xhr.send();
xhr.onreadystatechange = function()
{
if (xhr.readyState == 4)
{
return JSON.parse(xhr.responseText);
}
}
}
var resp = getURLInfo("http://example.com/") // resp always returns undefined...
Thanks in advance.
You are dealing with an asynchronous function call here. Results are handled when they arrive, not when the function finishes running.
That's what callback functions are for. They are invoked when a result is available.
function get(url, callback) {
var xhr = new XMLHttpRequest();
xhr.open("GET", url, true);
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
// defensive check
if (typeof callback === "function") {
// apply() sets the meaning of "this" in the callback
callback.apply(xhr);
}
}
};
xhr.send();
}
// ----------------------------------------------------------------------------
var param = "http://example.com/"; /* do NOT use escape() */
var finalUrl = "http://RESTfulAPI/info.json?url=" + encodeURIComponent(param);
// get() completes immediately...
get(finalUrl,
// ...however, this callback is invoked AFTER the response arrives
function () {
// "this" is the XHR object here!
var resp = JSON.parse(this.responseText);
// now do something with resp
alert(resp);
}
);
Notes:
escape() has been deprecated since forever. Don not use it, it does not work correctly. Use encodeURIComponent().
You could make the send() call synchronous, by setting the async parameter of open() to false. This would result in your UI freezing while the request runs, and you don't want that.
There are many libraries that have been designed to make Ajax requests easy and versatile. I suggest using one of them.
You can't do it at all for asynchronous XHR calls. You cannot make JavaScript "wait" for the HTTP response from the server; all you can do is hand the runtime system a function to call (your handler), and it will call it. However, that call will come a long time after the code that set up the XHR has finished.
All is not lost, however, as that handler function can do anything. Whatever it is that you wanted to do with a return value you can do inside the handler (or from other functions called from inside the handler).
Thus in your example, you'd change things like this:
xhr.onreadystatechange = function()
{
if (xhr.readyState == 4)
{
var resp = JSON.parse(xhr.responseText);
//
// ... whatever you need to do with "resp" ...
//
}
}
}
For small edit talking about post: https://stackoverflow.com/a/5362513/4766489
...
if (typeof callback == "function") {
//var resp = xhr.responseText;
var resp = JSON.parse(xhr.responseText);
callback(resp);
}
...
And when you call
...
function(data) {
alert(data);
/* now do something with resp */
}
...