Manage XHR Promises - javascript

So I've been looking into wrapping XHRs in a Promise. The issue I'm having is managing them. Is there a way I can manage the request in the Promise's .then(), instead of inside the body of the Promise itself?
var i = 0;
var requests = [];
while(i < 10) {
mkPromise().then(function (response) {
console.log(response) // handle fulfilled requests
})
.catch(function (error) {
console.log(error)
})
.then(function (response) {
console.log(requests) // manage requests here
});
i++
}
function mkPromise () {
var url = 'http://example.com';
return new Promise(function(resolve, reject) {
// Do the usual XHR stuff
var req = new XMLHttpRequest();
req.open('GET', url);
req.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
req.onload = function() {
// This is called even on 404 etc
// so check the status
if (req.status == 200) {
// Resolve the promise with the response text
resolve(req.response);
// Do this in the final .then() instead of here so that it's managed for errors as well without duplicating removal process
var index = requests.indexOf(req);
requests.splice(index,1)
}
else {
// Otherwise reject with the status text
// which will hopefully be a meaningful error
reject(Error(req.statusText));
}
};
// Handle network errors
req.onerror = function() {
reject(Error("Network Error"));
};
requests.push(req)
// Make the request
req.send();
});
}

Related

Using promise to call Ajax without duplicating code

is this possible? I want to write an ajax function, that I do not want to duplicate it. Pass it different parameter which are locations to different files. Then use the promise to make them into one object. I would possible use the spread operator. is this possible.
var myFuncCalls = 0;
let promiseAjax = new Promise (function ( resolve,reject) {
//possibly use a for look to grab the number of times the loadDoc was called then call the same function and send it to may be an array?
function loadDoc(location) {
myFuncCalls++;
console.log("loadDoc was called :" + myFuncCalls);
var xyz = new XMLHttpRequest();
xyz.onreadystatechange = function () {
if (this.readyState == 4 && this.status == 200) {
//console.log(this.responseText)
resolve(this.responseText);
}
};
xyz.open("GET", location, true);
xyz.send();
}
loadDoc("/_js/someitems.json");
loadDoc("/_js/someMoreItems.json");
})
// then grab all that stuff and make one single object using spread operators
promiseAjax.then(function (fromResolve){
// JSON.parse(fromResolve);
var newObj = JSON.parse(fromResolve);
console.log(newObj);
})
with Promise.all and Object.assign,
function loadDoc(location) {
return new Promise((resolve, reject) => {
var xyz = new XMLHttpRequest();
xyz.onreadystatechange = () => {
if (this.readyState == 4 && this.status == 200) {
resolve(JSON.parse(this.responseText));
} else {
// resolving with empty object to avoid breaking other fetch if one failed
resolve({});
}
};
xyz.open("GET", location, true);
xyz.send();
});
}
const loadDocs = (paths) => Promise.all(paths.map(path => loadDoc(path))
.then(results => {
// combine all result into single object
return Object.assign({}, ...results);
}));
// example
loadDocs([
"/_js/someitems.json",
"/_js/someMoreItems.json"
]).then(function(finalCombinedObject) {
// other logic here
});
Use Promise.all() to get the two calls together and so what ever you want with the array of the data you resolved.
function loadDoc(location) {
return new Promise (function ( resolve,reject) {
var xyz = new XMLHttpRequest();
xyz.onreadystatechange = function () {
if (this.readyState == 4 && this.status == 200) {
resolve(this.responseText);
}
};
xyz.open("GET", location, true);
xyz.send();
});
}
const urls = ["/_js/someitems.json", "/_js/someMoreItems.json"]
Promise.all(urls.map(url=>loadDoc(url))).then(responses =>
console.log(responses);
)
I think the easiest thing would be to define async functions, which return promises and can be easily passed around and reused.
You can do something like:
async function loadFile(file) {
...
return {...fileJSON};
}
async function loadFiles() {
const file1JSON = await loadFile('file1');
const file2JSON = await loadFile('file2');
return {...file1JSON, ...file2JSON};
}
loadFiles().then((combinedJSON) => {
...
})
These functions can take arguments and be reused like any other function.
This kind of behavior can archived with Promise.all Promise.all white the use of async+await async and the use of more state of the art calls (fetch) makes the code looks cleaner
async function loadAll(docs) {
return Promise.all(docs.map(async doc => {
const result = await fetch('http://example.com/movies.json');
return result.json();
}));
}
(async function() {
const responses = await loadAll(["/_js/someitems.json", "/_js/someMoreItems.json"]);
console.log(responses);
})();
Note: await can only be used from an async function.
Note2: the code is untested
Yes, youcan send the URL, any parameters, even the type of AJAX call (POST, GET, etc), to the method, then use it to build the call. This way, you can reuse the same method to do anything and everything you need to do from your client with a "simple" method call.
All code in this Answer is copied from the below link.
https://medium.com/front-end-weekly/ajax-async-callback-promise-e98f8074ebd7
function makeAjaxCall(url, methodType)
{
var promiseObj = new Promise(function(resolve, reject)
{
var xhr = new XMLHttpRequest();
xhr.open(methodType, url, true);
xhr.send();
xhr.onreadystatechange = function()
{
if (xhr.readyState === 4)
{
if (xhr.status === 200)
{
console.log("xhr done successfully");
var resp = xhr.responseText;
var respJson = JSON.parse(resp);
resolve(respJson);
}
else
{
reject(xhr.status);
console.log("xhr failed");
}
}
else {console.log('xhr processing going on');}
}
console.log("request sent succesfully");
});
return promiseObj;
}
enter code here
document.getElementById('userDetails').addEventListener('click', function()
{
// git hub url to get btford details
var userId = document.getElementById("userId").value;
var URL = "https://api.github.com/users/"+userId;
makeAjaxCall(URL, "GET").then(processUserDetailsResponse, errorHandler);
});
You can even send it the callback method. I also send it a method to use for errors.
function makeAjaxCall(url, methodType, callback)
{
$.ajax(
{
url : url,
method : methodType,
dataType : "json",
success : callback,
error : function (reason, xhr){
console.log("error in processing your request", reason);
}
});
}
// git hub url to get btford details
var URL = "https://api.github.com/users/btford";
makeAjaxCall(URL, "GET", function(respJson)
{
document.getElementById("userid").innerHTML = respJson.login;
document.getElementById("name").innerHTML = respJson.name;
document.getElementById("company").innerHTML = respJson.company;
document.getElementById("blog").innerHTML = respJson.blog;
document.getElementById("location").innerHTML = respJson.location;
});

Fake the response of a blocked XMLHttpRequest

I am intercepting an XMLHttpRequest and preventing it from making a network call.
There are third-party scripts that I want to prevent from performing retries when the XHR doesn't respond, and therefore I'd like to mimic a response with an OK status (i.e. readyState = 4). Is there a way to make this happen?
(function(XHR) {
var open = XHR.prototype.open;
var send = XHR.prototype.send;
var abort = XHR.prototype.abort;
XHR.prototype.open = function(method, url, async, user, pass) {
this._url = url;
open.call(this, method, url, async, user, pass);
};
XHR.prototype.send = function(data) {
var url = this._url;
try {
var canSend = checkCanSend(url);
if (!canSend) {
// TODO respond with OK status
this.abort();
} else {
send.call(this, data);
}
} catch (err) {
console.error(err);
}
}
XHR.prototype.abort = function() {
console.log('aborted ' + this._url);
abort.call(this);
}
})(XMLHttpRequest);

HTTP call gets cancelled due to another method call, how can I force prevent the second call untill the HTTP is response is received?

I'm doing authorization code flow for oauth2 and there is something I'm doing wrong here but I can't really detect..
Here is my code
In app.js
myService.setup().then(function(){...
In service.js
var service = {
setup(options) {
this.processData();
return this.getToken(options);
},
processData(data) {
let response = this._extractURLParams(window.location.href)
if (response.hasOwnProperty("code")) {
return this.handleAuthorizationCode(responseResult);
},
handleAuthorization(codeObject) {
var service= this;
return new Promise((resolve, reject) => {
var xhr = new XMLHttpRequest();
xhr.open("POST", /token, true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
var params = 'grant_type=authorization_code&code=' + codeObject.code + '&client_id=client_id&client_secret=secret&redirect_uri=' + codeObject.redirectURi;
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
var responseData = JSON.parse(this.response);
resolve(service.storeToken(responseData));
}
};
xhr.send(params);
});
},
getToken(options) {
let service = this;
return new Promise(function (resolve, reject) {
if(// localStorage has a token) {
resolve(localStorage.getToken()) //dummy text, real code here
} else {
resolve(service.handleRedirectionsFlow(options));
})
},
What happens is as follows
1) When I access my application, I call myService.setup()
2) the setup() method will call processData(), and since the url will be empty, the if condition will not pass and hence we will call getToken()
3) getToken() will call a method that will build a url and change the location to it so that we authenticate via a form on the authorization server and then we will redirect back to the application after authentication with a code!
4) after authentication, we will redirect to the application with something like
'url?code=abcasdasdsfdasifsfsfs
5) Now, processData() will detect that the url has code property and we will call handleAuthorizationCode
6) handleAuthorizationCode will simply do a post request to get a token and onReadyStateChange we will call another method to store the token.
7) now when we call getToken() from the setup(), at this point the onreadystatechange hasn't been triggered from the previous method, causing that we redo the redirect to authenticate again and then the token request gets cancelled and we never store it..
Could someone help me know where exactly I should put an extra promise and resolve it in order to call getToken() AFTER the onreadystatechange is striggered and the token is stored to avoid the infinite loop?
Thanks
It's hard to know without being able to run it but how about this?
var service = {
setup(options) {
return this.processData()
.then(token => token || this.getToken(options));
},
processData(data) {
const response = this._extractURLParams(window.location.href);
return response.hasOwnProperty("code")
? this.handleAuthorizationCode(responseResult)
: Promise.resolve();
},
handleAuthorization(codeObject) {
var service = this;
return new Promise((resolve, reject) => {
var xhr = new XMLHttpRequest();
xhr.open("POST", /token, true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
var params = 'grant_type=authorization_code&code=' + codeObject.code + '&client_id=client_id&client_secret=secret&redirect_uri=' + codeObject.redirectURi;
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
var responseData = JSON.parse(this.response);
resolve(service.storeToken(responseData));
}
};
xhr.send(params);
});
},
getToken(options) {
let service = this;
return new Promise(function(resolve, reject) {
if (localStorageHasToken) {
resolve(localStorage.getToken()) //dummy text, real code here
} else {
resolve(service.handleRedirectionsFlow(options));
}
});
}
};
Essentially we make processData always return a promise even if it's a promise that resolves immediately and in setup we wait for processData() promise to resolve before calling getToken.
I'm not sure what service.storeToken(responseData) returns but you can probably use it to skip calling getToken entirely if the token is already stored.

Chaining promises - correct methodology

I'm trying to create an excel add-in using Javascript that requires asynchronous functions return a JS promise to Excel and that the promise is resolved with the final value using the callback function. I am new to promises and have spent hours reading and testing this out with no success, and was hoping someone could help me understand what I'm doing wrong. Below is the code:
function TEST(num1) {
return new Promise(function (resolve) {
var corsproxy = "https://cors-anywhere.herokuapp.com/"
var apiurl = "https://randomapi.com/testapi"
var data = getData(corsproxy+apiurl).then(function(result){
console.log ("here it comes")
console.log(result.meta.status) /// This returns "Success"
return (result.meta.status) /// I need this to be resolved
})
console.log("last")
resolve(data)
})
};
/// Get JSON
function getData(url) {
return new Promise(function (resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'json';
xhr.onload = function () {
try {
if (xhr.status === 200) {
resolve(xhr.response);
}
else if (xhr.status !== 200) {
reject({
error: 'Request failed. ' + xhr.response
});
}
} catch (e) {
reject({
error: e
});
}
};
xhr.send();
});
}
The second function getData is working as intended and returning a JS Object. What I'm trying to accomplish with the TEST function is:
Create a new promise - this needs to be resolved/returned as "Success"
Call the API data with getData(temporarily running through a proxy to bypass CORS erros)
Extract the meta.status value ("Success")
I've tried a number of different things but the current code write "last" and resolves an undefined data before the getData function completes. Changing the "return (result.meta.status)" to "resolve (result.meta.status)" also doesn't help.
Any assistance with what I'm doing wrong would be greatly appreciated.
function TEST(num1) {
var corsproxy = "https://cors-anywhere.herokuapp.com/"
var apiurl = "https://randomapi.com/testapi"
return getData(corsproxy+apiurl)
}
TEST(valofnum1).then(function(result){
console.log ("here it comes")
console.log(result.meta.status) /// This returns "Success"
return (result.meta.status) /// Needs to be resolved
})
that's how you chain promises. you don't resolve a promise within another promise,
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises
You can use async/await since ES6 that solves a lot of this headache by simplifying the chain process into using await statements when letting promises resolve. I've updated your code block to use it, take a look:
async function TEST(num1) {
return new Promise(function (resolve) {
var corsproxy = "https://cors-anywhere.herokuapp.com/"
var apiurl = "https://randomapi.com/testapi"
var result = await getData(corsproxy+apiurl);
resolve(result.meta.status)
})
};
/// Get JSON
function getData(url) {
return new Promise(function (resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'json';
xhr.onload = function () {
try {
if (xhr.status === 200) {
resolve(xhr.response);
}
else if (xhr.status !== 200) {
reject({
error: 'Request failed. ' + xhr.response
});
}
} catch (e) {
reject({
error: e
});
}
};
xhr.send();
});
}
Changes made:
TESTis now an async function.
Rather chaining the resolved promise from getData and resolving in TEST, you simply await the response from getData and then resolve it.

Babel async await

I've spent last few hours trying to make it work but it just doesn't for no apparent reason. I have all the required packages and settings. I get no errors, async and await just doesn't wait.
I use Webpack to require the polyfill files that Babel adds, e.g babel-runtime/regenerator.
Code:
async function getData() {
let data = await ajaxCall();
console.log(data);
}
function ajaxCall() {
let url = 'https://maps.googleapis.com/maps/api/geocode/json?address=london';
let xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == XMLHttpRequest.DONE) {
if(xmlhttp.status == 200) {
console.log(JSON.parse(xmlhttp.response));
return JSON.parse(xmlhttp.response);
}
}
}
xmlhttp.open('GET', url, true);
xmlhttp.send();
}
getData();
// It logs "undefined" and then ajax response AFTER
.babelrc:
{
"presets": ["es2015", "stage-0"],
"plugins": ["transform-runtime"]
}
Does anyone have any idea what could be wrong?
In order for async and await to work you need to return something from your awaited call that the JS runtime can use to know when to continue the function that is awaiting the result. The "type" that is required for async / await interop is called Promise.
Your ajaxCall returns undefined, which doesn't tell the JS runtime anything, so it doesn't await because there is nothing to wait for. If you want to make this work, simply return a Promise from ajaxCall and resolve it when your ajax request is fulfilled.
At the simplest:
function ajaxCall() {
let url = 'https://maps.googleapis.com/maps/api/geocode/json?address=london';
// The new `window.fetch` API returns a promise for you
return fetch(url).then(response => response.json());
}
or using XMLHttpRequest:
function ajaxCall() {
let url = 'https://maps.googleapis.com/maps/api/geocode/json?address=london';
return new Promise((resolve, reject) => {
let xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = () => {
if (xmlhttp.readyState == XMLHttpRequest.DONE) {
if (xmlhttp.status == 200) {
// resolve instead of return inside of a Promise closure
resolve(JSON.parse(xmlhttp.response));
} else {
// reject instead of throw
// (will throw the error at the `await` expression.)
reject(Error(`Received status code ${xmlhttp.status}`));
}
}
}
xmlhttp.open('GET', url, true);
xmlhttp.send();
});
}
You need to return a promise from the ajaxCall().
Code should look something like that:
function ajaxCall() {
return new Promise((resolve, reject) => {
let url = 'https://maps.googleapis.com/maps/api/geocode/json?address=london';
let xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == XMLHttpRequest.DONE) {
if(xmlhttp.status == 200) {
console.log(JSON.parse(xmlhttp.response));
resolve(JSON.parse(xmlhttp.response));
}
}
// handle error: reject(error);
}
xmlhttp.open('GET', url, true);
xmlhttp.send();
})
}
then:
async function getData() {
try {
let data = await ajaxCall();
console.log(data);
} catch (e) {
// do something with error
}
}
Note that I used es6 arrow functions

Categories

Resources