Chaining promises - correct methodology - javascript

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.

Related

XMLHttpRequest GET function returning after sending the request. Need to figure out how to get function to wait for response

I am trying to get an array from my mongodb database, but am having problems setting the array to a variable to use in another function. I am calling the below method from another method that creates a table from the information from the database, but when I call this function it returns after the req.send() is called, not at the end of the function. So cart is empty since it does not wait to receive the response, it just returns to the function I am calling getFromAPI() from. Any advice would be greatly appreciated. Thanks!
function getFromAPI() {
var req = new XMLHttpRequest();
req.open('GET', '/products');
req.setRequestHeader('Content-Type', 'application/json');
req.send();
req.addEventListener('load', () => {
console.log(req.responseText);
var cart = req.responseText;
});
req.addEventListener('error', () => {
console.log('There was an error');
console.log(error);
});
}
You can use promises when dealing with asynchronous operations. More details can be found here.
The code above can be changed to the following...
function getFromAPI() {
return new Promise(function (resolve, reject) {
var req = new XMLHttpRequest();
req.open('GET', '/products');
req.setRequestHeader('Content-Type', 'application/json');
req.onload = resolve;
req.onerror = reject;
req.send();
});
}
You can then invoke the previous function as such...
getFromAPI()
.then(function (e) {
// perform some action on success. add items to cart, etc.
console.log(e.target.response);
}, function (e) {
// handle errors
});

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

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.

Ajax and async await

I am a bit confused as to why my ajax call doesnt return a result. I thought a method defined as async automatically returns a promise. What am I doing wrong?
async AjaxCall(filePath) {
let xhttp = new XMLHttpRequest();
xhttp.open('POST', filePath, true);
xhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhttp.send();
xhttp.onreadystatechange = function() {
if (xhttp.readyState === 4 && xhttp.status === 200) {
return xhttp.responseText;
}
}
}
async function Test() {
var result = await AjaxCall("file.php");
alert(result);
}
Test();
async/await is (really useful) syntactic sugar for creating and consuming promises. Your AjaxCall function implicitly creates a promise, but then also implicitly resolves it immediately with the value undefined because you never await anything, and the only return isn't directly in AjaxCall but is instead in the onreadystatechange callback.
You can wrap a promise around XHR, but you don't have to: fetch already does:
async function Test() {
var result = await fetch("file.php");
if (result.ok) {
alert(await result.text());
}
}
But if you want to do it yourself, you'll need to explicitly create and consume a promise rather than using async on AjaxCall:
function AjaxCall(filePath) {
return new Promise((resolve, reject) => {
let xhttp = new XMLHttpRequest();
xhttp.open('POST', filePath, true);
xhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhttp.send();
xhttp.onreadystatechange = function() {
if (xhttp.readyState === 4) {
if (xhttp.status === 200) {
resolve(xhttp.responseText);
} else {
reject(); // Probably including some rejection reason
}
}
};
});
}
The problem is that you aren't actually returning any data from your function. You are returning data inside the onreadystatechange function but that is just lost and never used. Take a look here specifically this piece of code:
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();
});
}
You will notice that it has wrapped the entire function in a promise and then you can use the standard async/await functionality when calling it. Async/Await is really just a wrapper around the existing promise functionality.

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