Can't make cross-domain requests without jQuery - javascript

I'm working on a web project where using jQuery (or other libraries/dependencies) is not an option, so I'm trying to replicate the code jQuery uses to make AJAX requests. The project previously used jQuery, so I've structured my replacement to the $.ajax() method to have the same behavior, however I cannot get mine to make cross-domain requests. When trying to load in scripts for example, I get this error in the console.
XMLHttpRequest cannot load <URL>. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin <URL> is therefore not allowed access.
I'm familiar with CORS and the whole cross-origin-security policy and what that entails, but what I'm confused about is how jQuery can seem to circumvent that, while my code cannot. I'm thinking there must be something special jQuery is doing?
This is how my $.ajax() replacement function is currently scripted.
function ajax (options) {
var request = new XMLHttpRequest();
if ("withCredentials" in request)
request.withCredentials = false;
else if (typeof XDomainRequest !== "undefined")
var request = new XDomainRequest();
options.type = options.type || options.method || "GET";
request.open(options.type.toUpperCase(), options.url, true);
for (var i in options.headers) {
request.setRequestHeader(i, options.headers[i]);
}
if (typeof options.beforeSend == "function")
var go = options.beforeSend(request, options);
if (go == false)
return false;
request.onreadystatechange = function () {
if (this.readyState === XMLHttpRequest.DONE) {
var resp = this.responseText;
try {
var body = JSON.parse(resp);
this.responseJSON = body;
}
catch (err) {
var body = resp;
}
if (this.status < 300 && typeof options.success == "function")
options.success(body, this.status, this);
else if (typeof options.error == "function")
options.error(this, this.status, body);
if (typeof options.complete == "function")
options.complete(this, this.status);
}
};
if (typeof options.data == "object" && options.data !== null)
options.data = JSON.stringify(options.data);
if (options.type.toUpperCase() != "GET")
request.send(typeof options.data !== "undefined" ? options.data : null);
else
request.send();
return request;
}
Can someone point out if I'm missing something obvious? Do I need to manually also do the OPTIONS pre-flight or something?

I've found a fix for this. The issue seemed to stem from trying to load in cross-domain scripts specifically. jQuery uses $.getScript() to do that, which actually just adds a script to the page instead of making a cross-domain HTTP request.
I used this code as a replacement for that function.
function getScript (url, callback) {
var loadScript = function (url, callback) {
var scrpt = document.createElement("script");
scrpt.setAttribute("type", "text/javascript");
scrpt.src = url;
scrpt.onload = function () {
scrpt.parentNode.removeChild(scrpt);
console.log("Script is ready, firing callback!");
if (callback)
callback();
};
document.body.appendChild(scrpt);
}
var isReady = setInterval(function () {
console.log(document.body);
if (document.body) {
clearInterval(isReady);
loadScript(url, callback);
}
}, 25);
}

Related

Function return from XMLHttpRequest [duplicate]

I'm trying to load JS scripts dynamically, but using jQuery is not an option.
I checked jQuery source to see how getScript was implemented so that I could use that approach to load scripts using native JS. However, getScript only calls jQuery.get()
and I haven't been able to find where the get method is implemented.
So my question is,
What's a reliable way to implement my own getScript method using native JavaScript?
Thanks!
Here's a jQuery getScript alternative with callback functionality:
function getScript(source, callback) {
var script = document.createElement('script');
var prior = document.getElementsByTagName('script')[0];
script.async = 1;
script.onload = script.onreadystatechange = function( _, isAbort ) {
if(isAbort || !script.readyState || /loaded|complete/.test(script.readyState) ) {
script.onload = script.onreadystatechange = null;
script = undefined;
if(!isAbort && callback) setTimeout(callback, 0);
}
};
script.src = source;
prior.parentNode.insertBefore(script, prior);
}
You can fetch scripts like this:
(function(document, tag) {
var scriptTag = document.createElement(tag), // create a script tag
firstScriptTag = document.getElementsByTagName(tag)[0]; // find the first script tag in the document
scriptTag.src = 'your-script.js'; // set the source of the script to your script
firstScriptTag.parentNode.insertBefore(scriptTag, firstScriptTag); // append the script to the DOM
}(document, 'script'));
use this
var js_script = document.createElement('script');
js_script.type = "text/javascript";
js_script.src = "http://www.example.com/script.js";
js_script.async = true;
document.getElementsByTagName('head')[0].appendChild(js_script);
Firstly, Thanks for #Mahn's answer. I rewrote his solution in ES6 and promise, in case someone need it, I will just paste my code here:
const loadScript = (source, beforeEl, async = true, defer = true) => {
return new Promise((resolve, reject) => {
let script = document.createElement('script');
const prior = beforeEl || document.getElementsByTagName('script')[0];
script.async = async;
script.defer = defer;
function onloadHander(_, isAbort) {
if (isAbort || !script.readyState || /loaded|complete/.test(script.readyState)) {
script.onload = null;
script.onreadystatechange = null;
script = undefined;
if (isAbort) { reject(); } else { resolve(); }
}
}
script.onload = onloadHander;
script.onreadystatechange = onloadHander;
script.src = source;
prior.parentNode.insertBefore(script, prior);
});
}
Usage:
const scriptUrl = 'https://www.google.com/recaptcha/api.js?onload=onRecaptchaLoad&render=explicit';
loadScript(scriptUrl).then(() => {
console.log('script loaded');
}, () => {
console.log('fail to load script');
});
and code is eslinted.
This polishes up previous ES6 solutions and will work in all modern browsers
Load and Get Script as a Promise
const getScript = url => new Promise((resolve, reject) => {
const script = document.createElement('script')
script.src = url
script.async = true
script.onerror = reject
script.onload = script.onreadystatechange = function() {
const loadState = this.readyState
if (loadState && loadState !== 'loaded' && loadState !== 'complete') return
script.onload = script.onreadystatechange = null
resolve()
}
document.head.appendChild(script)
})
Usage
getScript('https://dummyjs.com/js')
.then(() => {
console.log('Loaded', dummy.text())
})
.catch(() => {
console.error('Could not load script')
})
Also works for JSONP endpoints
const callbackName = `_${Date.now()}`
getScript('http://example.com/jsonp?callback=' + callbackName)
.then(() => {
const data = window[callbackName];
console.log('Loaded', data)
})
Also, please be careful with some of the AJAX solutions listed as they are bound to the CORS policy in modern browsers https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
There are some good solutions here but many are outdated. There is a good one by #Mahn but as stated in a comment it is not exactly a replacement for $.getScript() as the callback does not receive data. I had already written my own function for a replacement for $.get() and landed here when I need it to work for a script. I was able to use #Mahn's solution and modify it a bit along with my current $.get() replacement and come up with something that works well and is simple to implement.
function pullScript(url, callback){
pull(url, function loadReturn(data, status, xhr){
//If call returned with a good status
if(status == 200){
var script = document.createElement('script');
//Instead of setting .src set .innerHTML
script.innerHTML = data;
document.querySelector('head').appendChild(script);
}
if(typeof callback != 'undefined'){
//If callback was given skip an execution frame and run callback passing relevant arguments
setTimeout(function runCallback(){callback(data, status, xhr)}, 0);
}
});
}
function pull(url, callback, method = 'GET', async = true) {
//Make sure we have a good method to run
method = method.toUpperCase();
if(!(method === 'GET' || method === 'POST' || method === 'HEAD')){
throw new Error('method must either be GET, POST, or HEAD');
}
//Setup our request
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState == XMLHttpRequest.DONE) { // XMLHttpRequest.DONE == 4
//Once the request has completed fire the callback with relevant arguments
//you should handle in your callback if it was successful or not
callback(xhr.responseText, xhr.status, xhr);
}
};
//Open and send request
xhr.open(method, url, async);
xhr.send();
}
Now we have a replacement for $.get() and $.getScript() that work just as simply:
pullScript(file1, function(data, status, xhr){
console.log(data);
console.log(status);
console.log(xhr);
});
pullScript(file2);
pull(file3, function loadReturn(data, status){
if(status == 200){
document.querySelector('#content').innerHTML = data;
}
}
Mozilla Developer Network provides an example that works asynchronously and does not use 'onreadystatechange' (from #ShaneX's answer) that is not really present in a HTMLScriptTag:
function loadError(oError) {
throw new URIError("The script " + oError.target.src + " didn't load correctly.");
}
function prefixScript(url, onloadFunction) {
var newScript = document.createElement("script");
newScript.onerror = loadError;
if (onloadFunction) { newScript.onload = onloadFunction; }
document.currentScript.parentNode.insertBefore(newScript, document.currentScript);
newScript.src = url;
}
Sample usage:
prefixScript("myScript1.js");
prefixScript("myScript2.js", function () { alert("The script \"myScript2.js\" has been correctly loaded."); });
But #Agamemnus' comment should be considered: The script might not be fully loaded when onloadFunction is called. A timer could be used setTimeout(func, 0) to let the event loop finalize the added script to the document. The event loop finally calls the function behind the timer and the script should be ready to use at this point.
However, maybe one should consider returning a Promise instead of providing two functions for exception & success handling, that would be the ES6 way. This would also render the need for a timer unnecessary, because Promises are handled by the event loop - becuase by the time the Promise is handled, the script was already finalized by the event loop.
Implementing Mozilla's method including Promises, the final code looks like this:
function loadScript(url)
{
return new Promise(function(resolve, reject)
{
let newScript = document.createElement("script");
newScript.onerror = reject;
newScript.onload = resolve;
document.currentScript.parentNode.insertBefore(newScript, document.currentScript);
newScript.src = url;
});
}
loadScript("test.js").then(() => { FunctionFromExportedScript(); }).catch(() => { console.log("rejected!"); });
window.addEventListener('DOMContentLoaded',
function() {
var head = document.getElementsByTagName('HEAD')[0];
var script = document.createElement('script');
script.src = "/Content/index.js";
head.appendChild(script);
});
Here's a version that preserves the accept and x-requested-with headers, like jquery getScript:
function pullScript(url, callback){
pull(url, function loadReturn(data, status, xhr){
if(status === 200){
var script = document.createElement('script');
script.innerHTML = data; // Instead of setting .src set .innerHTML
document.querySelector('head').appendChild(script);
}
if (typeof callback != 'undefined'){
// If callback was given skip an execution frame and run callback passing relevant arguments
setTimeout(function runCallback(){callback(data, status, xhr)}, 0);
}
});
}
function pull(url, callback) {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState === XMLHttpRequest.DONE) {
callback(xhr.responseText, xhr.status, xhr);
}
};
xhr.open('GET', url, true);
xhr.setRequestHeader('accept', '*/*;q=0.5, text/javascript, application/javascript, application/ecmascript, application/x-ecmascript');
xhr.setRequestHeader('x-requested-with', 'XMLHttpRequest');
xhr.send();
}
pullScript(URL);

Detect 404 for resources via JavaScript

is there a way to detect resources on the page with response 404?
Also why the browser api- performance.getEntriesByType("resource") doesn't include the failed resources?
Well, with this function :
function UrlExists(url) {
var http = new XMLHttpRequest();
http.open('HEAD', url, false);
http.send();
if (http.status == 404) {
// do something
}
}
And you pass the URL of your resource. but it's not the best solution ever to check this. Let's say it's the simplest :)
EDIT :
After you can also do it for every kind of resources (CSS, Images, ...), a function like this one :
var styleSheetExists = function(name) {
for (var i in document.styleSheets) {
if (typeof document.styleSheets[i] == "object") {
link = document.styleSheets[i].href;
if (link === null) {
continue;
}
if (link.indexOf(name, link.length - name.length) !== -1) {
return true;
}
}
}
return false;
}
That you can use like :
$(document).ready(function() {
console.log(styleSheetExists('jquery-ui.css'));
console.log(styleSheetExists('doesnotexist.css'));
});
(Source of the function : How to check for 403 and 404 errors when changing the url of a resource?)
and by checking every kind of resource, you can assure that there is or not a 404 status about them.

Handle specific 404 error and get responseText with XDomainRequest for IE

I am using CORS to make cross domain call. My problem here is I need to handle the 404 error in XDomainRequest in IE.
In XDomainRequest I can only find onerror event to handle all the error event, but in my requirement I need to show empty placeholder images for 404 error case (The API doesn't provide empty 200 response for this,but use 404) but regardless other errors.
This is the request URL:
http://photorankapi-a.akamaihd.net/streams/1537083805/media/photorank?auth_token=11ff75db4075b453ac4a21146a210e347479b26b67b1a396d9e29705150daa0d&version=v2.1
In other browsers, I use XMLHttpRequest and req.status code == 404 to get the responseText:
{"metadata":{"code":404,"message":"Not Found","version":"v2.1"}}
But When I tried to get the responseText from req.onerror(below), I can get empty string only, so I can not tell the difference from 404 and other errors.
The part of code shows below:
xdr: function (url, method, data, callback, errback) {
var req;
if(typeof XDomainRequest !== "undefined") {
req = new XDomainRequest();
req.open(method, url);
req.onerror = function() {
// I want to handle specific 404 error here
callback(req.responseText);
};
req.onload = function() {
callback(req.responseText);
};
req.send(data);
} else if( typeof XMLHttpRequest !== "undefined") {
req = new XMLHttpRequest();
if('withCredentials' in req) {
req.open(method, url, true);
req.onerror = errback;
req.onreadystatechange = function() {
if (req.readyState === 4) {
if ((req.status >= 200 && req.status < 400) || req.status ==404 ) {
callback(req.responseText);
} else {
errback(new Error('Response returned with non-OK status'));
}
}
};
req.send(data);
}
} else {
errback(new Error('CORS not supported'));
}
}
The XMLHttpRequest works fine and all other browser are working except in IE.
Unfortunately the XDomainRequest object does not provide anyway of accessing the response HTTP status code. The only properties you can access on the XDR instance are: constructor, contentType, responseText, and timeout; and the only methods are: abort(), open(), and send(). On top of that, the error handler doesn't even get passed any arguments.

how to load json from url using javascript?

I'm new to javascript which should be really simple to solve, but I am lost as of now.
I have a url: http:getall.json
Using JavaScript (not JQuery or php. Just JavaScript), I want to read this JSON string and parse it. That's it.
access to your url doesn't work, you should show the JSON result. In javascript to get JSON object with AJAX request you can do something like this:
request = new XMLHttpRequest;
request.open('GET', 'http://v-apps-campaign.com/dunkindonuts/main/get_allStore', true);
request.onload = function() {
if (request.status >= 200 && request.status < 400){
// Success!
data = JSON.parse(request.responseText);
} else {
// We reached our target server, but it returned an error
}
};
request.onerror = function() {
// There was a connection error of some sort
};
request.send();
your result will be in the data variable.
JSONP calls:
function getJSONP(url, callback) {
var script = document.createElement('script');
var callbackName = "jsonpcallback_" + new Date().getTime();
window[callbackName] = function (json) {
callback(json);
};
script.src = url + (url.indexOf("?") > -1 ? "&" : "?") + 'callback=' + callbackName;
document.getElementsByTagName('head')[0].appendChild(script);
}
getJSONP("http://v-apps-campaign.com/dunkindonuts/main/get_allStore", function(jsonObject){
//jsonObject is what you want
});
Regular ajax ajax call:
function getXHR() {
if (window.XMLHttpRequest) {
return new XMLHttpRequest();
}
try {
return new ActiveXObject('MSXML2.XMLHTTP.6.0');
} catch (e) {
try {
// The fallback.
return new ActiveXObject('MSXML2.XMLHTTP.3.0');
} catch (e) {
throw new Error("This browser does not support XMLHttpRequest.");
}
}
}
function getJSON(url, callback) {
req = getXHR();
req.open("GET", url);
req.onreadystatechange = function () {
if (this.readyState == 4 && this.status == 200) {
var jsonObject = null,
status;
try {
jsonObject = JSON.parse(req.responseText);
status = "success";
} catch (e) {
status = "Invalid JSON string[" + e + "]";
}
callback(jsonObject, status, this);
}
};
req.onerror = function () {
callback(null, "error", null);
};
req.send(null);
}
getJSON("http://v-apps-campaign.com/dunkindonuts/main/get_allStore", function (jsonObject, status, xhr) {
//jsonObject is what you want
});
I tested these with your url and it seems like you should get the data with a jsonp call, because with regular ajax call it returns:
No 'Access-Control-Allow-Origin' header is present on the requested resource
with jsonp it gets the data but the data is not a valid json, it seems your server side has some php errors:
A PHP Error was encountered
...
In your HTML include your json file and a js code as modules
<script src="/locales/tshared.js" type="module" ></script>
<script src="/scripts/shared.js" type="module" ></script>
file content of tshared
export const loc = '{"en": { "key1": "Welcome" },"pt": {"key1": "Benvindo"} }'
file content of shared
import {loc} from "./../../locales/tshared.js";
var locale = null;
locale = JSON.parse(loc) ;
Adapt path and names as needed, use locale at will.

Callback of function stocked in an object

I need to do a sequential XMLHttpRequest requests (FIFO) to not to call the server with many requests a same time, I wrote this function that do the XMLHttpRequest sequentially:
var queue = [];
var xmlHttpCurrentlyOccuped = false;
function loadUserDetails() {
var url = "https://someurl.com";
doWebRequest("GET", url, "", parseUserDetails);
}
function parseUserDetails(dataFromServer){
Console.log("data received from server: "+JSON.stringify(dataFromServer));
}
function doWebRequest(method, url, params, callback) {
var parametres = new Object();
parametres.myMethod = method;
parametres.myUrl = url;
parametres.myParams = params;
parametres.myCallback = callback;
queue.push(parametres);
while (queue.length>0 && !xmlHttpCurrentlyOccuped){
var doc = new XMLHttpRequest();
doc.onreadystatechange = function() {
var status;
if (doc.readyState == XMLHttpRequest.LOADING || doc.readyState == XMLHttpRequest.OPENED){
xmlHttpCurrentlyOccuped = true;
}
if (doc.readyState == XMLHttpRequest.DONE && doc.status == 200) {
xmlHttpCurrentlyOccuped = false;
var data;
var contentType = doc.getResponseHeader("Content-Type");
data = doc.responseText;
queue[0].myCallback(data);
queue.shift();
}
else if (doc.readyState == XMLHttpRequest.DONE) {
xmlHttpCurrentlyOccuped = false;
status = doc.status;
if(status!=200) {
parseTheError(url);
}
queue.shift();
}
}
doc.open(queue[0].myMethod, queue[0].myUrl);
doc.send();
}
}
My problem is, after the XMLHttpRequest is done well, the callback function is not working in this line of my code queue[0].myCallback(data);I have this error: "queue[0].callback(data): undefined".
Any idea to resolve this issue?
Update:
I resolved the issue, this is my working code maybe it can help someone:
var queue = [];
function doWebRequest(method, url, params, callback) {
var parametres = new Object();
parametres.myMethod = method;
parametres.myUrl = url;
parametres.myParams = params;
parametres.myCallback = callback;
if (queue.length>0) {if (queue[queue.length-1].url != parametres.url) queue.push(parametres);}
else queue.push(parametres);
var doc = new XMLHttpRequest();
function processNextInQueue() {
if (queue.length>0){
var current = queue.shift();
doc.onreadystatechange = function() {
if (doc.readyState == XMLHttpRequest.DONE){
if(doc.status == 200) {
if(typeof current.myCallback == 'function'){
current.myCallback(doc.responseText)
} else {
console.log('Passed callback is not a function');
}
processNextInQueue();
}
else if(doc.status!=200) {
parseTheErrors(current.myUrl);
}
}
}
doc.open(current.myMethod, current.myUrl);
doc.send();
}
}
processNextInQueue();
}
Thank you guys for your help ;)
You can't poll in javascript with a while loop like this and expect proper performance. Javascript is single threaded so when you poll like this, you don't allow any cycles for other things to happen. You need to learn how to write asynchronous code where you start the first ajax call and then return. When that first one completes, you then start the second one and so on.
Here's a way to do this:
queue.push(parametres);
function processNextInQueue() {
if (queue.length) {
var doc = new XMLHttpRequest();
doc.onreadystatechange = function() {
if (doc.readyState == XMLHttpRequest.DONE) {
if (doc.status == 200) {
queue[0].myCallback(doc.responseText);
} else {
fonctionPourAnalyserLErreur(url);
}
// done now so remove this one from the queue
// and start the next one
queue.shift();
processNextInQueue();
}
}
doc.open(queue[0].myMethod, queue[0].myUrl);
doc.send();
}
}
processNextInQueue();
The idea is that you fire off the first ajax call and then you just return. When the readystatechange shows it is done, you process the results and then fire off the next one. All the while the ajax call is in process, the javascript engine is free to service other events and do other things (that's the key to handling an asynchronous operation like an ajax call).
In this line: queue[0].myCallback(data), for debugging purposes (and to prevent errors from breaking your site) I would change to the following:
var current = queue.shift();
if(typeof current.myCallback == 'function'){
current.myCallback(data)
} else {
// for now, log it
console.log('Passed callback is not a function');
}
Also, have you tried just passing an anonymous function to make sure it's not a scope/function hoisting issue?
function loadUserDetails() {
var url = "https://someurl.com";
doWebRequest("GET", url, "", function(dataFromServer){
console.log("data received from server: "+JSON.stringify(dataFromServer));
});
}

Categories