I try to get content with an async call and grab the response via an async function. What is wrong with this setup? The result of the console log is always undefined?
_json: function (callback) {
var xobj = new XMLHttpRequest();
xobj.overrideMimeType("application/json");
xobj.open('GET', this.options.url, true);
xobj.onreadystatechange = function () {
if (xobj.readyState == 4 && xobj.status == "200") {
callback(xobj.responseText);
}
};
xobj.send(null);
},
get: async function () {
var resp = await this._json(function(response) {
return JSON.parse(response);
});
console.log(resp);
}
In order to use async / await you need to know when to use await in the first place. You cannot just put simply await infront of anything and expect that it somehow awaits something.
So, when should you use await or lets say when does it make sense to use it?
One simple question:
Does your method / function returns an Promise?
Yes: You can put await infront of it
No: Its useless to use await
We can make that check on your issue:
Does this._json() returns an Promise?
In your case no. That means using await is useless.
What you can do is returning an promise:
_json: function () {
return new Promise((res, rej) => {
var xobj = new XMLHttpRequest()
xobj.overrideMimeType('application/json')
xobj.open('GET', this.options.url, true)
xobj.onreadystatechange = function () {
if (xobj.readyState == 4 && xobj.status === 200) {
res(xobj.responseText)
} else {
rej('Something went wrong')
}
}
xobj.send(null)
})
},
get: async function () {
var resp = await this._json()
console.log(resp)
},
this._json() returns an Promise now. In this case it makes sense to use await.
Remember: By putting async infront of an function,your function then returns also an promise.
Your _json function is not a Promise. It uses common callback pattern to pass information back asynchronously. You have to wrap it in a Promise first, if you want to use async/await syntax.
new Promise((resolve) => this._json((response) => resolve(JSON.parse(response)))
Then you can await it and receive parsed JSON (as you expect).
Related
I have a simple AJAX request which loads a .txt file:
var game = {
loadWordList : function(){
var request = new XMLHttpRequest();
request.open("GET", "https://gist.githubusercontent.com/deekayen/4148741/raw/01c6252ccc5b5fb307c1bb899c95989a8a284616/1-1000.txt", false);
request.onreadystatechange = function request_word_list(){
if(request.readyState === 4)
if(request.status === 200 || request.status == 0){
this.allWords = request.responseText.split('\n'); // MAIN ISSUE!
return true;
}
else
return false;
}
request.send(null);
},
The main issue is that after I declare the variable this.allWords it returns undefined after using it somewhere else.
For example, if I type:
console.log(game.allWords);
The output will be undefined and not the list of words.
How can I make JavaScript wait until AJAX completes its request?
What you need are Promises, async/await and the new fetch API.
(async () => {
const game = {};
game.loadWordList = () => {
return new Promise(async (resolve, reject) => {
let res = await fetch("https://gist.githubusercontent.com/deekayen/4148741/raw/01c6252ccc5b5fb307c1bb899c95989a8a284616/1-1000.txt");
let text = await res.text();
resolve(text.split('\n'))
})
}
try {
game.allWords = await game.loadWordList();
console.log(game.allWords);
}
catch(err) {
throw Error(err); // something went wrong with the request
}
})()
A promise is used when you need to get a job done but you don't know how much time it will take or in what order it will occur,
the promise can either be resolved, which means the task ran successfully, or it can be rejected meaning there was an error.
fetch is a promise based API, which is more or less the modern replacement for XMLHttpRequests,
by using the concepts of async/await you can make your code "wait" until a promise has resolved by using the await keyword,
here we are waiting until the fetch has successfully retrieved your data, and then only allowing the rest of the code following it to execute, if any error occurs, the try catch block will handle it.
Here are some resources if you want to look into the above mentioned topics in more detail,
https://javascript.info/promise-basics
https://javascript.info/async-await
I want to create a listener function to listen changes of global variable. The use case is, when I made a ajax call, i flag isAjaxDone variable to false, once its done then flag it to true. So the listener will process something once detected the isAjaxDone is true.
i tried asyc ... await & Promise but i still can't achieve what i want. The entire method still run asynchronously outside, except the method inside asyc ... await & Promise.
Here is what i have tried:
var isAjaxDone = null
var timeout = 0
function listener(){
let waiter = function(){
return new Promise(resolve=>{
setTimeout(() => {
timeout += 1
listener()
}, 100);
})
}
if(isAjaxDone) return true
if(isAjaxDone === null) return false
if(isAjaxDone === false){
if(timeout < 300){
return waiter()
}
else{
return "timeout"
}
}
}
Implementation:
function checker(){
var ajaxStatus = listner()
if(ajaxStatus){
//...
}
}
When i call isAjaxDone, it will return me a Promise function instead of boolean.
I prefer not to use Promise...then because the function i wrote is consider as library, I don't want the user wrap a bunch of code inside the then because it will caused some problem to user code structure. Same goes to the Callback.
I would like to let it wait until either return timeout or boolean only, please advise.
You could do something like:
var xhr = new XMLHttpRequest;
xhr.open('POST', yourURLHere);
var promise = new Promise(function(resolve, reject){
xhr.onload = function(){
var o = JSON.parse(this.responseText); // should be `json_encode(['testProperty' => true])`ed response
if(o.testProperty === true){
resolve(true);
}
else{
reject(false);
}
}
xhr.send(formData); // you should get used to FormData
});
promise.then(function(resolveResult){
yourGlobalVar = resolveResult;
});
Of course, I would just make a post function and execute a function when all is complete, like:
function post(url, formData, success, successContext){
var xhr = new XMLHttpRequest;
var c = successContext || this;
xhr.open('POST', url);
xhr.onload = function(){
success.call(c, JSON.parse(xhr.responseText));
}
xhr.send(formData);
return xhr;
}
Now you can reuse:
var fd = new FormData;
fd.append('output', true);
// PHP could look like
/* if(isset($_POST['output']) && $_POST['output'] === true){
echo json_encode(['result' => true]);
}
*/
post('someURL.php', fd, function(resultObj){
globalVar = resultObj.result; // should be true since we sent that
});
Why not use an AJAX API that is already awaitable like fetch? It is polyfillable.
const request = new Request('https://example.com', {method: 'POST', body: '{"foo": "bar"}'});
(async() => {
const response = await fetch(request);
return await response.json();
})()
So I'm using puppeteer in nodeJS and I have a function that recursively calls itself until a specified page is correctly loaded:
const isPageAccessible = async (page, url) => {
var pageRes = await page.goto(url, {
timeout: 10000
}).catch(e => console.log(e));
if (!pageRes || pageRes.status !== 200) {
console.log("Website didn't load properly.")
isPageAccessible(page, url);
} else {
console.log("Loaded!")
return true;
}
}
The problem here is that this function is returning undefined after the first recursive call (which is normal as far as I know since async functions must resolve with a value). I want the code to wait until this function resolves with true
console.log(await isPageAccessible(page,LOGIN_URL));
console.log("Done!")
So the console would log "Done!" after the website has been loaded succesfully. Right now it is logging "Done!" even if the website hasn't been loaded since isPageAccessible function is returning undefined after the first call.
Any thoughts on how to solve this would be appreciated!
You need to return the recursive call up the Promise chain:
if (!pageRes || pageRes.status !== 200) {
console.log("Website didn't load properly.")
return isPageAccessible(page, url);
}
Instead use this npm module,
https://www.npmjs.com/package/sync-request
var request = require('sync-request');
var res = request('GET', 'http://example.com');
console.log(res.getBody());
console.log("Done!")
All the statements will be executed one after another after completion.
Lesser code i guess!!
Using ES6, Classes, and Aync/Await...
The goal is to have an "Api" class that does async calls with fetch, and returns some data.... but even the basic foundation isn't working.
in the main js these snippet runs, which starts the chain of events:
let api = new Api();
let api_data = api.getLocation();
console.log(api_data);
getLocation method is the following, which would return some response/data. However, that data would theoretically be a fetch call to an API, which is "getTestVariable" for example that waits some time...
class Api {
getLocation = async () => {
var response = await this.getTestVariale();
console.log(response);
return response;
}
getTestVariale = () =>{
setTimeout(function(){
console.log("timeout done...");
return "this is the test variable";
},2000);
}
}
However, 1) the console.log(response) gives "undefined" because its not awaiting... and 2) back in the main js, api_data when logged, is some Promise object rather than the variable response
As the comment above states, setTimeout does not return a Promise, so getTestVariable has no return value.
Here's a slightly modified version of your code that will hopefully put you on the right track:
class Api {
getLocation = async () => {
var response = await this.getTestVariale();
console.log(response);
return response;
}
getTestVariale = () => {
return new Promise((resolve, reject) => {
if (thereIsError)
reject(Error('Error message'));
else
resolve({foo: 'bar'});
}
}
}
Drop a comment if I need to explain further I'd be happy to.
In getLocation you await for a value that will come from this.getTestVariable. In order for this to work this.getTestVariable must return a Promise; it can be done in two ways - making getTestVariable an async function or explicitly returning a Promise.
Since you're using setTimeout (which is not an async function) you're bound to use Promise. Here you go:
class Api {
async getLocation() {
return await this.getTestVariable();
}
getTestVariable() {
return new Promise((res, rej) => {
setTimeout(() => res('test'), 2000)
});
}
}
async function main() {
let api = new Api;
console.log('Trying to get the location...');
console.log('Voila, here it is: ', await api.getLocation());
}
main();
Looks quite ugly but there's no way you can achieve it if you use set timeout.
The main point is in resolution of getTestVariable with value you want it to return.
Quite an important remark: you can mark getTestVariable as an async function, it will ad an extra Promise level, but you still will have the desired result.
Here is the code I'm working with (IP address censored for obvious reasons):
async function buildJobsView() {
let jobList = await getJobs()
Promise.all([getJobs()]).then($("#jobsPane").text(jobList))
}
async function getJobs() {
//Open API connection and submit
var url = "http://IPADDRESS:8082/api/jobs?IdOnly=true"
var xhr = new XMLHttpRequest()
xhr.open("GET", url, true)
xhr.send()
xhr.onreadystatechange = function() {
if(xhr.readyState == 4 && xhr.status == "200") {
return xhr.response
}
}
}
For whatever reason, the jobList variable is being assigned before the getJobs() function finishes running. The getJobs() function does return the right output eventually, but the code has already moved on. What am I doing wrong?
async doesn't automatically convert callback-based code into Promise-based code - you have to explicitly convert the callback to a Promise and return a Promise whenever you want to be able to use it as a Promise.
function getJobs() {
return new Promise((resolve) => {
//Open API connection and submit
var url = "http://IPADDRESS:8082/api/jobs?IdOnly=true"
var xhr = new XMLHttpRequest()
xhr.open("GET", url, true)
xhr.send()
xhr.onreadystatechange = function() {
if(xhr.readyState == 4 && xhr.status == "200") {
resolve(xhr.response)
}
}
});
}
Then, getJobs will return a Promise, and then you can consume it with await:
const jobList = await getJobs()