delay foreach until xmlhttprequest finishes the work - javascript

I tried to find an answer in StackOverflow and even searched elsewhere on the Internet, but I've had no luck. I have been trying for a couple of days now with no success.
I just need to delay my forEach until the XMLHttpRequest is completed for each file, based on my code below. Thanks in advance..
function handleFiles(files,i) {
files = [...files]
files.forEach(uploadFile)
}
function uploadFile(file, i) {
var Url_imageAdd = "mytargetUrl...."
var xhr = new XMLHttpRequest()
var formData = new FormData()
xhr.open('POST', Url_imageAdd, true)
xhr.upload.addEventListener("progress", function(e) {
updateProgress(i, (e.loaded * 100.0 / e.total) || 100)
})
xhr.addEventListener('readystatechange', function(e) {
if (xhr.readyState == 4 && xhr.status == 200) {
document.getElementById('lConfirm').innerHTML="Files uploaded...."
urlCount = parseInt($('#urlCount').val())
$('#urlCount').val(urlCount+filescount)
var imageSet = JSON.parse(xhr.responseText)
} else if (xhr.readyState == 4 && xhr.status != 200) {
alert("Upload Error....")
window.location.reload();
}
})
formData.append('img_file', file)
formData.append('title',"mytitle")
xhr.send(formData)
}

If your uploadFile function returns a Promise, then you can use await or a .then(...) callback with Promise.all:
function uploadFile(file, i) {
return new Promise((resolve, reject) => {
var Url_imageAdd = "mytargetUrl...."
var xhr = new XMLHttpRequest()
var formData = new FormData()
xhr.open('POST', Url_imageAdd, true)
xhr.upload.addEventListener("progress", function(e) {
updateProgress(i, (e.loaded * 100.0 / e.total) || 100)
})
xhr.addEventListener('readystatechange', function(e) {
if (xhr.readyState == 4 && xhr.status == 200) {
document.getElementById('lConfirm').innerHTML="Files uploaded...."
urlCount = parseInt($('#urlCount').val())
$('#urlCount').val(urlCount+filescount)
var imageSet = JSON.parse(xhr.responseText)
resolve(imageSet)
} else if (xhr.readyState == 4 && xhr.status != 200) {
reject(new Error("Upload Error...."))
}
})
formData.append('img_file', file)
formData.append('title',"mytitle")
xhr.send(formData)
});
}
Now you can do this:
async function handleFiles(files,i) {
files = [...files]
try {
await Promise.all(files.map(uploadFile));
// We've finished
} catch (err) {
alert('Upload failed!');
window.location.reload();
}
}
Or if you want to run them in series instead of parallel, with a 2 second delay between:
function delay(milliseconds) {
return new Promise(resolve => {
setTimeout(resolve, milliseconds);
});
}
async function handleFiles(files,i) {
files = [...files]
for (let i = 0; i < files.length; i++) {
try {
await delay(2000);
await uploadFile(files[i], i);
} catch (err) {
alert('Upload failed!');
window.location.reload();
}
}
// We've finished
}

It depends on your technical constraints.
With the latest technology, try to use await/async call:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
Otherwise, improve your solution, hold the result of each XMLHttpRequest and process after all are done.

I would recommend two changes:
Change the uploadFile function to return a promise
Use a normal for loop, instead of forEach to iterate and await for each file.
Check this.

There are many possible solutions for this like for example using promise or callback But I will share you a solution using recursion method:
Edit: Updated to avoid global variable
function handleFiles(files, i) {
files = files;
count = files.length;
currentFileIndex = 0;
uploadFile(files);
}
function uploadFile(files) {
const file = files[0]
.
.
.
xhr.addEventListener('readystatechange', function(e) {
if (xhr.readyState == 4 && xhr.status == 200) {
document.getElementById('lConfirm').innerHTML="Files uploaded...."
urlCount = parseInt($('#urlCount').val())
$('#urlCount').val(urlCount+filescount)
var imageSet = JSON.parse(xhr.responseText)
// Recursion
let moreFiles = files.slice(1);
if (moreFiles.length) {
uploadFile(moreFiles);
}
} else if (xhr.readyState == 4 && xhr.status != 200) {
alert("Upload Error....")
window.location.reload();
}
})
.
.
.
}

Related

How to stop a while loop when a file is not found that is using XMLHttpRequest()'s?

I want to loop through local files and grab the information from them. map1.txt map2.txt ...
However when a file does not exist the code does not stop. The rawFile.status returns 404 and the console says it is on the rawFile.send(null) line. It just keeps going through the loop never ending and giving the same error.
I want it to stop making requests once the file does not exist.
i = 0;
while (i != -1) {
(function(i) {
var rawFile = new XMLHttpRequest();
rawFile.open("GET", "../maps/map" + [i] + ".txt", false);
rawFile.onreadystatechange = function() {
if (rawFile.status == 404) {
break; //(or i = -1;)
}
if (rawFile.readyState === 4) {
if (rawFile.status === 200 || rawFile.status == 0) {
//do stuff
}
}
};
rawFile.send(null);
})(i);
i++;
}
onreadystatechange will be called asynchronously. You need to wait for the request to be done before scheduling the next one.
Solution 1: Callbacks
You can handle this by giving your function a callback that it can call once the request is done (like nodejs does for all its asynchronous APIs).
Typically the callback will have the function(error, result) {} signature - the first being either an error object if the function encountered an error or null in case it didn't, and the second one being the result of the async action.
Example with callbacks:
function getFile(i, cb) {
var rawFile = new XMLHttpRequest();
rawFile.open("GET", "../maps/map" + [i] + ".txt", false);
rawFile.onreadystatechange = function() {
if (rawFile.status == 404) {
cb(new Error("It's dead Jim!"), null);
}
if (rawFile.readyState === 4) {
if (rawFile.status === 200 || rawFile.status == 0) {
//do stuff
cb(null, "RESULT");
}
}
};
rawFile.send(null);
}
function doUntilNotFound(i = 0) {
getFile(i, (error, result) => {
if(error) {
// handle error
} else {
// do something with result
doUntilNotFound(i + 1);
}
});
}
doUntilNotFound();
However callbacks can become quite ugly after a few nestings - often referred to as callback hell.
Solution 2: Promises
The newer, more modern way of doing async things is async / await and Promises which will make your code easier to understand:
function getFile(i) {
return new Promise((resolve, reject) => {
var rawFile = new XMLHttpRequest();
rawFile.open("GET", "../maps/map" + [i] + ".txt", false);
rawFile.onreadystatechange = function() {
if (rawFile.status == 404) {
reject(new Error("File not found"));
}
if (rawFile.readyState === 4) {
if (rawFile.status === 200 || rawFile.status == 0) {
//do stuff
resolve("RESULT");
}
}
};
rawFile.send(null);
});
}
async function downloadFiles() {
try {
for(let i = 0;; i++) {
let result = await getFile(i);
}
} catch(e) {
// handle file not found here
}
}
downloadFiles();
Solution 3: Using Promises + fetch
Also you cam use fetch instead of XMLHttpRequest if you're already working with Promises:
async function downloadFiles() {
try {
for(let i = 0;; i++) {
const req = await fetch("../maps/map" + [i] + ".txt");
const reader = req.body.getReader();
// TODO: Read Response body, do something with it
}
} catch(e) {
// handle file not found here
}
}
downloadFiles();
This will make your code even shorter & simpler :)
I like Turtlefights answer, but am offering an async alternative instead of going recursive:
first, start by making the request into a promise-returning function
function getFile(i) {
return new Promise((resolve, reject) => {
var rawFile = new XMLHttpRequest();
rawFile.open("GET", "../maps/map" + [i] + ".txt", false);
rawFile.onreadystatechange = function() {
resolve(rawFile)
};
rawFile.send(null);
})
}
then, in your main function (which should be labeled async so you can await stuff), you can await each one in the foreach and check the reponse status, and break if it 404s. Something like this:
async function checkFiles() {
let i = 0;
const responses = [];
while (i != -1) {
const response = await getFile(i);
if (response.status == 404) break;
responses.push(response);
i++;
}
return responses;
}
You might want to set up your getFile function with timeout conditions as well

How to concat two called endpoints to one string and print it in console

My function has to call two endpoints and concat them in one string at the same time. My code is simply a function that is getting two endpoints at the same time and print it in console.
But the same function has to concat them to one string.
I tried to create separated variables contains each call and then simply concat them, but the result hadn't been any different.
I read about it for couple of hours, and I see no, even the smallest tip anywhere.
EDIT: Please mind that each endpoint is an actual array.
function endpointsToOneString() {
const Http = new XMLHttpRequest();
const url = 'https://baconipsum.com/api/?type=all-meat&paras=3&start-with-lorem=1&format=json';
Http.open("GET", url);
Http.send();
Http.onreadystatechange = function () {
if (this.readyState == 4 && this.status == 200) {
console.log(Http.responseText)
}
}
const HttpTwo = new XMLHttpRequest();
const urlTwo = 'https://baconipsum.com/api/?type=all-meat&paras=3&start-with-lorem=1&format=json';
HttpTwo.open("GET", urlTwo);
HttpTwo.send();
HttpTwo.onreadystatechange = function () {
if (this.readyState == 4 && this.status == 200) {
console.log(Http.responseText)
}
}
}
endpointsToOneString();
In this case you should use Promise feature of javascript.
Here you can learn how to promisify your native XHR. Morever, Here you can find about promise chaining.
I have just added Promise in your code but it needs to be refactored.
Update: From comment, you want your response texts as a plain string. But we are actually getting a JSON array as response. So, we need to parse it using JSON.parse() function to make it an array object. Then we need to use .join() method to join all element of the array into a string. See the code below:
function endpointsToOneString() {
var requestOne = new Promise(function(resolve, reject){
const Http = new XMLHttpRequest();
const url = 'https://baconipsum.com/api/?type=all-meat&paras=3&start-with-lorem=1&format=json';
Http.open("GET", url);
Http.onload = function () {
if (this.status >= 200 && this.status < 300) {
resolve(Http.response);
} else {
reject({
status: this.status,
statusText: Http.statusText
});
}
};
Http.onerror = function () {
reject({
status: this.status,
statusText: Http.statusText
});
};
Http.send();
});
var requestTwo = new Promise(function(resolve, reject){
const HttpTwo = new XMLHttpRequest();
const urlTwo = 'https://baconipsum.com/api/?type=all-meat&paras=3&start-with-lorem=1&format=json';
HttpTwo.open("GET", urlTwo);
HttpTwo.onload = function () {
if (this.status >= 200 && this.status < 300) {
resolve(HttpTwo.response);
} else {
reject({
status: this.status,
statusText: HttpTwo.statusText
});
}
};
HttpTwo.onerror = function () {
reject({
status: this.status,
statusText: HttpTwo.statusText
});
};
HttpTwo.send();
});
Promise.all([
requestOne,
requestTwo
]).then(function(result){
var response = JSON.parse(result[0]).join();
response += JSON.parse(result[1]).join();
console.log(response);
});
}
endpointsToOneString();
I understand you want to concat the result of two parallel requests. In that case you can use a library like axios. From their docs
function getUserAccount() {
return axios.get('/user/12345');
}
function getUserPermissions() {
return axios.get('/user/12345/permissions');
}
axios.all([getUserAccount(), getUserPermissions()])
.then(axios.spread(function (acct, perms) {
// Both requests are now complete
}));
So for your example:
function getEndpoint1() {
return axios.get('https://baconipsum.com/api/?type=all-meat&paras=3&start-with-lorem=1&format=json');
}
function getEndpoint2() {
return axios.get('https://baconipsum.com/api/?type=all-meat&paras=3&start-with-lorem=1&format=json');
}
axios.all([getEndpoint1(), getEndpont2()])
.then(axios.spread(function (resp1, resp2) {
// Both requests are now complete
console.log(resp1 + resp2)
}));
try to have a look on the Promise.all method:
https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
As in this answer you should wrap your XHR in a Promise and then handle resolving of all function call. In this way you can access endpoint results in order.
Here's a working fiddle:
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();
});
}
let url1 = 'https://baconipsum.com/api/?type=all-meat&paras=3&start-with-lorem=1&format=json';
let url2 = 'https://baconipsum.com/api/?type=all-meat&paras=3&start-with-lorem=1&format=json'
Promise.all([makeRequest('GET', url1), makeRequest('GET', url2)])
.then(values => {
debugger;
console.log(values);
});
https://jsfiddle.net/lbrutti/octys8k2/6/
Is it obligatory for you to use XMLHttpRequest? If not, u had better use fetch, because it returns Promise and with Promise it would be much simpler.
Rather than immediately printing them, save them to local variables, then print them at the end:
function endpointsToOneString() {
let response; // this line here declares the local variable
results = 0; // counts results, successful or not
const Http = new XMLHttpRequest();
const url = 'https://baconipsum.com/api/?type=all-meat&paras=3&start-with-lorem=1&format=json';
Http.open("GET", url);
Http.send();
Http.onreadystatechange = function () {
if (this.readyState == 4 && this.status == 200) {
response = Http.responseText; //save one string
}
if (this.readyState == 4) {
results++;
}
}
const HttpTwo = new XMLHttpRequest();
const urlTwo = 'https://baconipsum.com/api/?type=all-meat&paras=3&start-with-lorem=1&format=json';
HttpTwo.open("GET", urlTwo);
HttpTwo.send();
HttpTwo.onreadystatechange = function () {
if (this.readyState == 4 && this.status == 200) {
response += HttpTwo.responseText // save the other string
}
if (this.readyState == 4) {
results++;
}
}
while(results < 2) {} //loops until both requests finish, successful or not
console.log(response); //print total string
}
endpointsToOneString();
Also, HttpTwo's onreadystatechange function is calling for Http.responseText, rather than HttpTwo.responseText. Fix that as well for best results.
EDIT: Thanks for the tip, Jhon Pedroza!
EDIT: Noah B has pointed out that the above is dirty and inefficient. They are entirely correct. Better version based on their suggestion, credit to them:
function endpointsToOneString() {
let response1 = '', response2 = ''; // this line declares the local variables
const Http = new XMLHttpRequest();
const url = 'https://baconipsum.com/api/?type=all-meat&paras=3&start-with-lorem=1&format=json';
Http.open("GET", url);
Http.send();
Http.onreadystatechange = function () {
if (this.readyState == 4 && this.status == 200) {
response1 = Http.responseText; //save one string
checkResults(response1, response2);
}
}
const HttpTwo = new XMLHttpRequest();
const urlTwo = 'https://baconipsum.com/api/?type=all-meat&paras=3&start-with-lorem=1&format=json';
HttpTwo.open("GET", urlTwo);
HttpTwo.send();
HttpTwo.onreadystatechange = function () {
if (this.readyState == 4 && this.status == 200) {
response2 = HttpTwo.responseText; // save the other string
checkResults(response1, response2);
}
}
}
function checkResults(r1, r2) {
if (r1 != '' && r2 != '') {
console.log(r1 + r2);
}
}
endpointsToOneString();
function endpointsToOneString() {
var response;
const Http = new XMLHttpRequest();
const url = 'https://baconipsum.com/api/?type=all-meat&paras=3&start-with-lorem=1&format=json';
Http.open("GET", url);
Http.send();
Http.onreadystatechange = function () {
if (this.readyState == 4 && this.status == 200) {
response = this.responseText;
HttpTwo.open("GET", urlTwo);
HttpTwo.send();
}
}
const HttpTwo = new XMLHttpRequest();
const urlTwo = 'https://baconipsum.com/api/?type=all-meat&paras=3&start-with-lorem=1&format=json';
HttpTwo.onreadystatechange = function () {
if (this.readyState == 4 && this.status == 200) {
response += this.responseText;
console.log(response);
}
}
}
endpointsToOneString();
check this out. there's just minimal editing to your code.

Why this XMLHttpRequest doesn't work in IE?

I have a very simple XMLHttpRequest making a POST call. It works on Chrome and Firefox. But when I tried it in IE10, I got this error..
SCRIPT5022: InvalidStateError
Here's the code.
function _ajaxHelper(options) {
var xhr = new XMLHttpRequest();
var opts = _extendHelper({
withCredentials: false,
method: 'GET'
}, options);
xhr.withCredentials = opts.withCredentials;
xhr.open(opts.method, opts.url);
xhr.setRequestHeader('Accept', 'text/plain');
xhr.send(JSON.stringify(options.data));
return {
done: function done(cb) {
xhr.onreadystatechange = function onStateChange() {
if (this.readyState === 4) {
if (this.status >= 200 && this.status < 300) {
cb(this.responseText);
} else {
cb(false);
}
}
};
xhr.ontimeout = function onTimeout() {
cb(false);
};
xhr.onerror = function onError() {
cb(false);
};
}
};
}
From what I googled, the reason that I would get that error was I set responseText but I didn't set anything and I'm getting instead. So, not sure what I did wrong there.
For _extendHelper this is the code
function _extendHelper() {
var args = arguments;
var res = {};
if (args && args[0]) {
for (var j in args) {
for (var k in args[j]) {
res[k] = args[j][k];
}
}
}
return res;
}

'Debugging connection was closed. Reason: Render process gone.' while trying to upload big/multiple files using FileReader

I want to upload some files. If the file is smaller than 1gb, upload it entirely.
If the file is bigger than 1gb, upload it as segments of maximum 1gb each.
My code works correctly on small files, but when I try to upload bigger files, the browser crashes. Error "Debugging connection was closed. Reason: Render process gone."
The file that I was making tests with is 4,3 GB, and will start 5 XHR request.
What am I doing wrong?
let count = 0;
for (let url of this.urls) {
var start = 0 + (idx * 1000000000);
var stop = 0;
if (this.urls.length === 1)
stop = files[0].size;
else if (this.urls[this.urls.length - 1] == url)
stop = start + (files[0].size % 1000000000);
else
stop = start + 1000000000;
var reader = new FileReader();
// If we use onloadend, we need to check the readyState.
reader.onloadend = (event) => {
if (reader.readyState === 2) {
this._httpService.xhrBlobPut(url, reader.result).subscribe(result => {
count += 1;
if (count == this.urls.length) {
this._storageService.postFile(this.appId, this.fileId, payload).subscribe(result => {
});
}
});
}
};
var blob = files[0].slice(start, stop + 1);
reader.readAsArrayBuffer(blob);
this.isUploaded = true;
idx += 1;
}
xhrBlobPut(url: string, body: any): Observable<any> {
return Observable.fromPromise(new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200 || xhr.status === 201) {
resolve(xhr.response)
} else {
reject(xhr.response)
}
}
}
xhr.open("PUT", url);
xhr.send(body);
}));
}

Ajax without jQuery [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
How to make an ajax call without jquery?
I have code in js(Ajax) but I want to make it without Ajax(XMLHttpRequest)
$.ajax({
type:'POST',
data:'slug='+prize,
url:'/wybrana-nagroda/',
success:function(msg)
{
$('#whaiting').hide();
if(msg==='winner')
$(window.location).attr('href','/formularz');
}
});
How it should look?
function send(post, url) {
var client = new XMLHttpRequest();
client.open("POST", url);
client.send(message);
}
?
Thanks.
If you want it to be compatible on all browsers, you'll need to do something like the following:
function sendRequest(url,callback,postData) {
var req = createXMLHTTPObject();
if (!req) return;
var method = (postData) ? "POST" : "GET";
req.open(method,url,true);
req.setRequestHeader('User-Agent','XMLHTTP/1.0');
if (postData)
req.setRequestHeader('Content-type','application/x-www-form-urlencoded');
req.onreadystatechange = function () {
if (req.readyState != 4) return;
if (req.status != 200 && req.status != 304) {
// alert('HTTP error ' + req.status);
return;
}
callback(req);
}
if (req.readyState == 4) return;
req.send(postData);
}
var XMLHttpFactories = [
function () {return new XMLHttpRequest()},
function () {return new ActiveXObject("Msxml2.XMLHTTP")},
function () {return new ActiveXObject("Msxml3.XMLHTTP")},
function () {return new ActiveXObject("Microsoft.XMLHTTP")}
];
function createXMLHTTPObject() {
var xmlhttp = false;
for (var i=0;i<XMLHttpFactories.length;i++) {
try {
xmlhttp = XMLHttpFactories[i]();
}
catch (e) {
continue;
}
break;
}
return xmlhttp;
}
Credit: quirksmode

Categories

Resources