How to cache XMLHttpRequest response in Javascript? - javascript

I have a function that loads my html templates asynchronously:
loadTplAsync: function(path) {
return Q.Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.open("GET", path, true);
xhr.onload = () => {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
resolve(_.template(xhr.responseText));
} else {
reject(xhr.responseText);
}
}
};
xhr.onerror = error => reject(error);
xhr.send(null);
});
}
How to extend this function to cache responses by browser?

Assuming that what you mean by cache is not to repeat making same request during life cycle of that page load you could store the promise as a variable and return the same promise each time.
The first time a specific path is requested will make a new request, subsequent times only the stored promise will be returned
var promises ={};
loadTplAsync: function(path) {
// create new promise if it doesn't already exist for path instance
if(!promises[path]){
promises[path] = Q.Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.open("GET", path, true);
xhr.onload = () => {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
resolve(_.template(xhr.responseText));
} else {
reject(xhr.responseText);
}
}
};
xhr.onerror = error => reject(error);
xhr.send(null);
});
}
// return the stored promise
return promises[path];
}
Note this is not a persistent cache and new requests would be made on subsequent page loads

Related

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));
});
}());

Function within function 3 times over in Javascript

I am kinda new to Javascript and have written some Chrome Extension. The code contains a sequence process where one function is being passed to another and will be called when the first is done. To be frank, this is getting complicated for me to even look at.
I will explain with an example that shows the sequence I have in my code:
function successFunc() {
console.log("Success!");
}
function handleErrorFunc(successFunc) {
... stuff to handle error...
step1(successFunc);
}
function step1(successFunc) {
var url = ...
var request = new XMLHttpRequest();
request.open("GET", url, true);
request.send();
request.onerror = function() {
...
}
request.onreadystatechange = function() {
if (request.readyState == 4 && request.status == 200) {
...
step2(successFunc)
}
}
}
function step2(successFunc) {
var url = ...
var request = new XMLHttpRequest();
request.open("GET", url, true);
request.send();
request.onerror = function() {
...
}
request.onreadystatechange = function() {
if (request.readyState == 4 && request.status == 200) {
...
successFunc()
}
}
}
Now I call
step1(successFunc);
Is there something to prevent this from happening, some design pattern perhaps?
Many javascript libraries return a Promise from an ajax call. This is a preferable method to using callbacks as it neatens your code considerable.
Instead of the code you currently have, your code would instead look like this.
step1().then(step1Result => step2(step1Result))
.catch(err => console.error("step1 faled",err));
You can continue to chain this as appropriate
step1().then(step1Result => step2(step1Result)
.then(step2Result => step3(step2Result))
.catch(err => console.error("step2 failed",err))
)
.catch(err => console.error("step1 faled",err));
Using the standard XMLHttpRequest does not follow this pattern, but it is straightforward to wrap in a Promise if thats what you want to do - however it is much easier to use an existing ajax library which supports promises.
I would suggest you start by reading Using Promises guide.
edit: If you want to wrap your calls in an Promise make sure you pass back the response as well as the errors. eg:
function ajaxGet(url){
return new Promise((resolve, reject) => {
var request = new XMLHttpRequest();
request.open("GET", url, true);
request.send();
request.onerror = reject; // will pass back error
request.onreadystatechange = function() {
if (request.readyState == XMLHttpRequest.DONE && request.status == 200) {
resolve(request.responseText);
}
}
});
}
Then step1 might be
function step1(){
return ajaxGet("url/for/step1");
}
What you want to look at is promises in Javascript. But before you dive into something that specific I would recommend you get a basic understanding of functional programming in Javascript.
A few links to help you out in this case are given below -
Functional Programming in JS
Promises in JS
A better approach to your problem is to use promises :
function successFunc() {
console.log("Success!");
}
// now returns a promise and does not need successFunc
function step1() {
return new Promise((resolve, reject) => {
const request = new XMLHttpRequest();
request.open("GET", 'your/url', true);
request.send();
request.onerror = function() {
reject();
}
request.onreadystatechange = function() {
if (request.readyState == 4 && request.status == 200) {
resolve();
}
}
});
}
// now returns a promise and does not need successFunc
function step2() {
return new Promise((resolve, reject) => {
var request = new XMLHttpRequest();
request.open("GET", 'your/url', true);
request.send();
request.onerror = function() {
reject();
}
request.onreadystatechange = function() {
if (request.readyState == 4 && request.status == 200) {
resolve();
}
}
});
}
function handleErrorFunc(step) {
if (step === 1) {
// handle step1 errors...
}
else if (step === 2) {
// handle step2 errors...
}
}
// chain your step calls
step1()
.then(() => {
step2()
.then(() => {
successFunc();
})
.catch(() => {
handleErrorFunc(2);
});
})
.catch(() => {
handleErrorFunc(1);
});

I need to read a text file from a javascript

I'm writing webpage with a javascript to read data files in text format from the server per user request. Once the text file has been loaded, I need to manipulate the data somewhat.
I have been using XMLHttpRequest for the loading, however, now I see that synchronous requests are "deprecated". I can't start manipulating the data before it's loaded, so what can I do in this case?
Use an asynchronous request (or fetch, see below, which is also asynchronous):
function doGET(path, callback) {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
// The request is done; did it work?
if (xhr.status == 200) {
// ***Yes, use `xhr.responseText` here***
callback(xhr.responseText);
} else {
// ***No, tell the callback the call failed***
callback(null);
}
}
};
xhr.open("GET", path);
xhr.send();
}
function handleFileData(fileData) {
if (!fileData) {
// Show error
return;
}
// Use the file data
}
// Do the request
doGET("/path/to/file", handleFileData);
Or using promises, which are the more modern way to handle callbacks (but keep reading):
function doGET(path, callback) {
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
// The request is done; did it work?
if (xhr.status == 200) {
// Yes, use `xhr.responseText` to resolve the promise
resolve(xhr.responseText);
} else {
// No, reject the promise
reject(xhr);
}
}
};
xhr.open("GET", path);
xhr.send();
});
}
// Do the request
doGET("/path/to/file")
.then(function(fileData) {
// Use the file data
})
.catch(function(xhr) {
// The call failed, look at `xhr` for details
});
Here in 2019, there's no reason to use XHR wrapped in a promise like that, just use fetch:
function doGET(url) {
return fetch(url).then(response => {
if (!response.ok) {
throw new Error("HTTP error " + response.status); // Rejects the promise
}
});
}
Since you want to handle the local file, Try this
Make use of XMLHttpRequest
function readFile(file)
{
var f = new XMLHttpRequest();
f.open("GET", file, false);
f.onreadystatechange = function ()
{
if(f.readyState === 4)
{
if(f.status === 200 || f.status == 0)
{
var res= f.responseText;
alert(res);
}
}
}
f.send(null);
}
Then you have to call with File:\\
readFile('File:\\\yourpath');

Chained promises give undefined function error, but I can execute the functions separately

I'm trying to chain some API calls before setting the textContent of some spans in my webpage. I can execute the following ajax API calls separately by pasting them into the console, but when I chain them as promises I get getFirstData() is undefined.
var first_data = [],
second_data = [];
function getFirstData(){
var xhr = new XMLHttpRequest();
var url = "/API/first-data?format=json"
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
first_data = JSON.parse(xhr.responseText);
return Promise.resolve('1');
}
}
xhr.open("GET", url, true);
xhr.send();
}
/*getSecondData is the same, but with a different API url. I'll DRY these
two into one function that takes a url argument when I get it working.*/
getFirstData().then(getSecondData).then(createPage);
This is between <script> tags just before </body>. So what's wrong with the call to getFirstData() on the last line that causes the interpreter to say it's undefined? For reference, in the network log, getSecondData() is sent and returns just fine.
(Note: I'm specifically trying to do this without JQuery).
The issue occurs because your function is returning undefined (in other words, getting to the end of the function block before it returns) before it ever gets a chance to return Promise.resolve('1').
Your function has to immediately return a Promise object, which becomes pending before eventually resolving inside your AJAX handler.
I'd also add error handling using the provided reject argument, as is standard for Promise objects.
function getFirstData(){
return new Promise(function(resolve, reject) { // encapsulate code in a promise which returns immediately
var xhr = new XMLHttpRequest();
var url = "/echo/json"
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
first_data = JSON.parse(xhr.responseText);
return resolve('1');
}
else {
return reject('There was an error!') // reject the promise if error occurs
}
}
xhr.open("GET", url, true);
xhr.send();
});
}
And then catch it in the thenable chain:
getFirstData()
.then(getSecondData)
.catch(function(err){Throw err}) // catch the error if it throws
.then(createPage);
See working jsfiddle
getFirstData isn't returning a promise it returns undefined, which is not thenable.
function getFirstData(){
return new Promise(function(resolve) {
var xhr = new XMLHttpRequest();
var url = "/API/first-data?format=json"
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
first_data = JSON.parse(xhr.responseText);
resolve('1');
}
}
xhr.open("GET", url, true);
xhr.send();
});
}

how to make my async function a promise in angular?

I'm working in AngularJS and i have a service in which the post method is:
var xhrService = {
post: function(url, data) {
xhr = new XMLHttpRequest();
xhr.open("POST", url);
xhr.send(data);
return xhr;
}}
I want to be able to use this service just like the built-in (angular) $http service with a success & error methods.
It would be very good if no other library is used.
You will need at least $q service to create deferred, you return deferred.promise from the function. Then you either resolve or reject it based on the result.
var deferred = $q.defer();
var req = new XMLHttpRequest();
req.open('GET', url);
req.onload = function() {
if (req.status == 200) {
deferred.resolve(req.response);
}
else {
deferred.reject(req.statusText);
}
}
req.onerror = function() {
deferred.reject('error');
};
req.send();
return deferred.promise;

Categories