Javascript ES6 Promises with map - javascript

I'm trying to figure out the most efficient way to wait inside map function until all datas are fetched and then continue. I tried with the 'bluebird' library and came out with this.
Is this even working right and is there even better way to achieve this?
let urls = ['https://static.pexels.com/photos/36753/flower-purple-lical-blosso.jpg', 'https://static.pexels.com/photos/36764/marguerite-daisy-beautiful-beauty.jpg', 'https://static.pexels.com/photos/65609/tulip-tulips-sharpness-game-flower-65609.jpeg'];
let test = [];
Promise.map(urls, function(url) {
// Promise.map awaits for returned promises as well.
return getThumb(url);
}).then(function() {
console.log(test);
});
function getThumb(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.onload = () => resolve(xhr.responseText);
xhr.onerror = () => reject(xhr.statusText);
xhr.send();
test.push(url);
});
https://jsfiddle.net/v80wmmsv/4/
Thanks :)
Edit:
This is the final outcome:
let urls = ['https://static.pexels.com/photos/36753/flower-purple-lical-blosso.jpg', 'https://static.pexels.com/photos/36764/marguerite-daisy-beautiful-beauty.jpg', 'https://static.pexels.com/photos/65609/tulip-tulips-sharpness-game-flower-65609.jpeg'];
Promise.map(urls, getThumb).then(function(data) {
console.log(data.length);
}).catch(e => console.error(e));
function getThumb(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.onload = () => resolve(xhr.responseText);
xhr.onerror = () => reject(xhr.statusText);
xhr.send();
});
};

If you want to concurrently run all promises and do something when all of them are resolved, you can use es2015 Promise.all():
let urls = ['https://static.pexels.com/photos/36753/flower-purple-lical-blosso.jpg', 'https://static.pexels.com/photos/36764/marguerite-daisy-beautiful-beauty.jpg', 'https://static.pexels.com/photos/65609/tulip-tulips-sharpness-game-flower-65609.jpeg'];
let test = [];
Promise.all(urls.map(getThumb)).then(function() {
console.log(test);
});
function getThumb(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.onload = () => resolve(xhr.responseText);
xhr.onerror = () => reject(xhr.statusText);
xhr.send();
test.push(url);
});
};

Some notes about that code, which may or may not be issues depending on what you want:
test will end up with the URLs in the order in which they were requested (which I'm fairly sure is the same as their order in the original array, looking at the Promise.map documentation), not the order in which they were resolved (if you care)
test will contain the URLs even if they failed to load, since you're pushing them in the mapping function
You're resolving your individual promises with the text (well, image data) of the response, but not using that text anywhere
You're using an unnecessary wrapper function, no need for the wrapper around getThumb:
Promise.map(urls, getThumb).then(function() {
console.log(test);
});
And one definite issue:
You're not handling failure: You need a catch (or a then with the second [failure] callback).
Other than the lack of error handling, if the above is what you want, that code is fine.

Related

Async Await Javascript Function Not Awaiting Promise Before Executing Further Code [duplicate]

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]-->

How to call a promise function which will load content on a page dynamically? [duplicate]

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]-->

In JavaScript how do I/should I use async/await with XMLHttpRequest?

Full disclosure: I'd qualify myself as having intermediate JavaScript knowledge. So this is slightly above my experience level at this time.
I've got a Google Chrome Extension that does an AJAX request for a local file:/// as soon as a page loads. After I get the response back from the request I use the returned code in several functions later on in my code. Most of the time I get the response back before my code that needs it runs. But sometimes I don't and everything breaks.
Now, I assume I could just throw all of the relevant code inside of the xhr.onload below. But that seems really inefficient? I have a lot of moving parts that rely on the response and it seems bad to put them all in there.
I've perused several articles related to async/await and I'm having trouble grasping the concept. I'm also not 100% positive I'm looking at this the right way. Should I even be considering using async/await?
Here is the code for my AJAX request.
var xhr = new XMLHttpRequest();
xhr.open("GET", url, true);
xhr.onload = function(e) {
code = xhr.response;
};
xhr.onerror = function () {
console.error("** An error occurred during the XMLHttpRequest");
};
xhr.send();
Let's say I've got a bunch of functions that need to fire afterwards later on in my code. Right now they just look like:
function doTheThing(code) {
// I hope the response is ready.
}
What's the best way to approach this? FYI, the Fetch API isn't an option.
Here's a high level view of how my code is structured.
// AJAX request begins.
// ...
// A whole bunch of synchronous code that isn't dependant on
// the results of my AJAX request. (eg. Creating and appending
// some new DOM nodes, calculating some variables) I don't want
// to wait for the AJAX response when I could be building this stuff instead.
// ...
// Some synchronous code that is dependant on both my AJAX
// request and the previous synchronous code being complete.
// ...
// Some more synchronous code that needs the above line to
// be complete.
I usually do async/await like this:
async function doAjaxThings() {
// await code here
let result = await makeRequest("GET", url);
// code below here will only execute when await makeRequest() finished loading
console.log(result);
}
document.addEventListener("DOMContentLoaded", function () {
doAjaxThings();
// create and manipulate your DOM here. doAjaxThings() will run asynchronously and not block your DOM rendering
document.createElement("...");
document.getElementById("...").addEventListener(...);
});
Promisified xhr function here:
function makeRequest(method, url) {
return new Promise(function (resolve, reject) {
let 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();
});
}
I create a promise for the XHR. Then simply use await inside an async function to call it.
function getHTML(url) {
return new Promise(function (resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.open('get', url, true);
xhr.responseType = 'document';
xhr.onload = function () {
var status = xhr.status;
if (status == 200) {
resolve(xhr.response.documentElement.innerHTML);
} else {
reject(status);
}
};
xhr.send();
});
}
async function schemaPageHandler(){
try {
var parser = new window.DOMParser();
var remoteCode = await getHTML('https://schema.org/docs/full.html');
var sourceDoc = parser.parseFromString(remoteCode, 'text/html');
var thingList = sourceDoc.getElementById("C.Thing");
document.getElementById("structured-data-types").appendChild(thingList);
} catch(error) {
console.log("Error fetching remote HTML: ", error);
}
}
You get two options,
first is to use newer fetch api which is promise based, with with you can do
let response = await fetch(url);
response = await response.json();; // or text etc..
// do what you wanna do with response
Other option if you really want to use XMLHttpRequest is to promisify it
let response = await new Promise(resolve => {
var xhr = new XMLHttpRequest();
xhr.open("GET", url, true);
xhr.onload = function(e) {
resolve(xhr.response);
};
xhr.onerror = function () {
resolve(undefined);
console.error("** An error occurred during the XMLHttpRequest");
};
xhr.send();
})
// do what you wanna do with response
possible full solution
(async () => {
let response = await new Promise(resolve => {
var xhr = new XMLHttpRequest();
xhr.open("GET", url, true);
xhr.onload = function(e) {
resolve(xhr.response);
};
xhr.onerror = function () {
resolve(undefined);
console.error("** An error occurred during the XMLHttpRequest");
};
xhr.send();
})
doTheThing(response)
})()
I had the same problem and solved it using the following function:
const makeRequest = (method, url, data = {}) => {
const xhr = new XMLHttpRequest();
return new Promise(resolve => {
xhr.open(method, url, true);
xhr.onload = () => resolve({
status: xhr.status,
response: xhr.responseText
});
xhr.onerror = () => resolve({
status: xhr.status,
response: xhr.responseText
});
if (method != 'GET') xhr.setRequestHeader('Content-Type', 'application/json');
data != {} ? xhr.send(JSON.stringify(data)) : xhr.send();
})
}
const test = async() => {
console.log("Starting request ...")
let request = await makeRequest("GET", "https://jsonplaceholder.typicode.com/todos/1");
console.log("status:", request.status)
console.log("response:", request.response)
}
test()
You can for example create an asynchronous class to use instead of the original one. It lacks some methods but it can serve as an example.
(function() {
"use strict";
var xhr = Symbol();
class XMLHttpRequestAsync {
constructor() {
this[xhr] = new XMLHttpRequest();
}
open(method, url, username, password) {
this[xhr].open(method, url, true, username, password);
}
send(data) {
var sxhr = this[xhr];
return new Promise(function(resolve, reject) {
var errorCallback;
var loadCallback;
function cleanup() {
sxhr.removeEventListener("load", loadCallback);
sxhr.removeEventListener("error", errorCallback);
}
errorCallback = function(err) {
cleanup();
reject(err);
};
loadCallback = function() {
resolve(xhr.response);
};
sxhr.addEventListener("load", loadCallback);
sxhr.addEventListener("error", errorCallback);
sxhr.addEventListener("load", function load() {
sxhr.removeEventListener("load", load);
resolve(sxhr.response);
});
sxhr.send(data);
});
}
set responseType(value)
{
this[xhr].responseType = value;
}
setRequestHeader(header, value) {
this[xhr].setRequestHeader(header, value);
}
}
addEventListener("load", async function main() {
removeEventListener("load", main);
var xhra = new XMLHttpRequestAsync();
xhra.responseType = "json";
xhra.open("GET", "appserver/main.php/" + window.location.hash.substring(1));
console.log(await xhra.send(null));
});
}());

Webdriverio execute custom command wait for async call on the browser side

I have one problem
Here is my custom command
client.addCommand('getBinaryImage', function (message) {
var self = this;
return self.execute(
function downloadImageBinary(url) {
var err = null;
var result = null;
function toDataURL(url, callback) {
var xhr = new XMLHttpRequest();
xhr.onload = function () {
var reader = new FileReader();
reader.onloadend = function () {
callback(reader.result);
};
reader.readAsDataURL(xhr.response);
};
xhr.open('GET', url);
xhr.responseType = 'blob';
xhr.send();
}
toDataURL(url, function (dataUrl) {
alert('RESULT:' + dataUrl);
// Now return the result !!
});
}
);
});
And I am using it as follows
let chain = client
.url("https://stackoverflow")
.getBinaryImage("image url here")
.then((result) => {
console.log(result);
})
But as you can see the execution of the script on the browsers side is async.
The problem is that I need to execute this script sync in the chain order, so until the result is not ready it should not reach the then handler in the chain.
Please suggest how to implement this ?
You need to return a promise that resolves only when you get your data back.
I'm using http://bluebirdjs.com as the promises library, they also have a nice and simple example over there - http://bluebirdjs.com/docs/api/new-promise.html
wrap your entire function with
return new Promise(function (resolve, reject) {
and when you get the result (your // Now return the result !! code) invoke resolve(dataUrl)

XMLHttpRequest Progress Events within a Promise

I'm having trouble finding any solutions for tracking the upload progress event of an XMLHttpRequest object inside of a Promise.
Here is an example of the code that I'm using to create async requests:
var request = function(method, url, data) {
return new Promise(function(resolve, reject) {
xhr.open(method, url, true);
xhr.onload = resolve;
xhr.onerror = reject;
xhr.send(data);
});
};
I'd like to have something like this:
var r = request('POST', '/upload', data)
.then(() => {
console.log('completed');
});
r.upload.addEventListener('progress', (e) => {
console.log('ProgressEvent: ', e);
});
I'm trying to do this with vanilla JavaScript which is working well so far, but I'd like to figure out a clean way to do this without using any third party libraries.
Unfortunately base promises don't have a way of representing any kind of progress. They are only pass/fail and can only send one result. You can think of them more as a delayed function return rather than a callback. You will probably need to accept a progress callback from your function and send that information out an alternate route. If you use a promise library some of them do have a progress callback option (ex. https://github.com/kriskowal/q search for "Progress Notification")
With some caveats for older browsers, you can use ES6 classes to extend the Promise constructor and add your own API on top.
Here is a basic example adding an onReadyStateChange event handler to a Promise AJAX wrapper.
'use strict';
const _onReadyStateChange = Symbol('onReadyStateChange');
class PromiseAJAX extends Promise {
constructor(func) {
let promise;
super((resolve, reject) => {
func(resolve, reject, function(event) {
// Constructor will not finish before readyState 1 event.
// That means we will not have access to promise in time.
// Could use a setTimeot of 0 if desired to capture.
let onReadyStateChange;
if (promise && (onReadyStateChange = promise.onReadyStateChange)) {
onReadyStateChange(event);
}
});
});
promise = this;
this[_onReadyStateChange] = undefined;
}
get onReadyStateChange() {
return this[_onReadyStateChange];
}
set onReadyStateChange(value) {
this[_onReadyStateChange] = value;
}
}
////////////////////////////////////////////////////////////////////////////////
let pajax = new PromiseAJAX(function(resolve, reject, readyStateChange) {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = readyStateChange;
xhr.onload = resolve;
xhr.onerror = reject;
xhr.open('GET', 'https://crossorigin.me/https://www.google.com/humans.txt', true);
xhr.send();
});
pajax.then(function(value) {
console.log(value.target.responseText);
});
pajax.catch(function(reason) {
console.log(reason);
});
pajax.onReadyStateChange = function(event) {
console.log('readyState: ' + event.target.readyState);
};
It's not entirely perfect, but should give you the basics of extending a Promise if desired.

Categories