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
Related
I found I code snippet in W3Schools.com (visit site). It works fine, but according Promise syntax, if there is failure (error) we have to use myReject. I have been changed from myResolve to myReject. It has not worked. What is the point?
async function getFile() {
let myPromise = new Promise(function(myResolve, myReject) {
let req = new XMLHttpRequest();
req.open('GET', "mycar.html");
req.onload = function() {
if (req.status == 200) {
myResolve(req.response);
} else {
myResolve("File not Found"); //It should to be myReject.
}
};
req.send();
});
document.getElementById("demo").innerHTML = await myPromise;
}
getFile();
A promise reject (in this case, myReject) is like throwing an error in a function.
await is basically calling the promise and waiting for the response.
To catch an error, we can use a try...catch block for async/await functions:
async function getFile() {
let myPromise = new Promise(function(myResolve, myReject) {
let req = new XMLHttpRequest();
req.open('GET', "mycar.html");
req.onload = function() {
if (req.status === 200) {
myResolve(req.response);
} else {
myReject("File not Found"); //Changed to myReject.
}
};
req.send();
});
try {
const response = await myPromise;
document.getElementById("demo").innerHTML = response;
}
catch(error){
document.getElementById("demo").innerHTML = "An error occurred: " + error;
}
}
getFile();
In addition, you can call re-write the code to have getFile handle the error:
async function getFile() {
let myPromise = new Promise(function(myResolve, myReject) {
let req = new XMLHttpRequest();
req.open('GET', "mycar.html");
req.onload = function() {
if (req.status === 200) {
myResolve(req.response);
} else {
myReject("File not Found"); //Changed to myReject.
}
};
req.send();
});
return await myPromise;
}
getFile().then(function(response){
document.getElementById("demo").innerHTML = response;
}).catch(function(error){
document.getElementById("demo").innerHTML = "An error occurred: " + error;
});
More reading on async/await: https://javascript.info/async-await
Also, like Francesco Lisandro said: a server can return different OK responses other than 200.
For simplicity, sure we can use req.status, but we can also use req.statusText that should give us "OK" if the response is OK.
First you need to to reject your promise in case file is not found which you have already figured out how to do. Now, a promise being rejected is an exception which we need to handle here.
async function getFile() {
let myPromise = new Promise(function(myResolve, myReject) {
let req = new XMLHttpRequest();
req.open('GET', "mycar.html");
req.onload = function() {
if (req.status == 200) {
myResolve(req.response);
} else {
myReject("File not Found");
}
};
req.send();
});
try{
document.getElementById("demo").innerHTML = await myPromise;
} catch(e){
document.getElementById("demo").innerHTML = "I got an error" ;
}
}
getFile();
First, in that case, the error can be different from File Not Found (status code is 404). Take a look at https://developer.mozilla.org/en-US/docs/Web/HTTP/Status. Additionally you need a way to separate success cases to error cases and that's why you can use it. In your case you can use reject but you should then use .catch() to handle the error when you call getFile().
async function getFile() {
let myPromise = new Promise(function(myResolve, myReject) {
let req = new XMLHttpRequest();
req.open('GET', "mycar.html");
req.onload = function() {
if (req.status == 200) {
myResolve(req.response);
} else {
myReject("File not Found");
}
};
req.send();
});
document.getElementById("demo").innerHTML = await myPromise;
}
getFile().catch((e)=>console.log(e));
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;
});
Yes, I want to do it completely synchronous. I know that it will completely stop my one and only thread, but I really need that, because I use some SDK which I don't want to change and in this SDK you need to pass a function that will be called and that will change some value in there like that:
function onNonce(stuff) {
const url = 'fancy url to change stuff';
// await also doesn't work
// const response = await fetch(url);
// const resp_json = await response.json();
// return resp_json.token;
// await also doesn't work
const req = new XMLHttpRequest();
req.open("GET", url, false); // <-- completely sync and deprecated
req.send();
if(req.readyState === 4 && req.status === 200) {
return req.response.token;
}
}
And this is how my func is called:
function SDK(result) {
//
// SOME FANCY CODE
//
var the_value_to_change;
the_value_to_change = onNonce('some stuff');
console.log("async");
//
// SOME FANCY CODE that uses this the_value_to_change
//
}
If I use await then my func returns Promise instead of the token, and if I use open with true (async), then I get undefined. The variant with false (completely sync) is deprecated, so I want to do the same stuff with fetch API.
// EDIT //
So, how can I do the execution of onNonce function (fetch and response.json()) completely synchronous?
You can't.
fetch does not have an option to run synchronously (for the same reason that the option to run XHR synchronously was deprecated in the first place).
Learn how to use await properly instead.
function onNonce() {
const url = 'fancy url to change stuff';
return new Promise(function (resolve, reject) {
const req = new XMLHttpRequest();
req.open('GET', 'url', true);
req.onreadystatechange = function () {
if (req.readyState === 4 && req.status === 200) {
resolve(req.responseText);
}
};
req.send();
});
}
async SDK() {
var the_value_to_change;
the_value_to_change = onNonce();
let result = await the_value_to_change;
}
This should work. You have to resolve a Promise with a value.
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.
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.