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
Related
I want to run an Ajax request after another Ajax request has fired. If there are multiple Ajax request(Requests A and B, and I want to fire request C if A is fired.) in the same page, how can I target the request I want? What do I need to run to fill up the code below?
xhr.addEventListener('readystatechange', function(event) {
if (xhr.readyState === 4 && xhr.status === 200 && Request A fired) {
Make Request C;
}
});
I am looking for a solution without jQuery.
You could make your request into a function that accepts a callback function. This function is called whenever the request is completed and will continue your code with the data that is received.
function request(url, callback) {
const xhr = new XMLHTTPRequest();
xhr.addEventListener('readystatechange', function(event) {
if (xhr.readyState === 4 && xhr.status === 200 && typeof callback === 'function') {
callback(xhr.responseText);
}
});
xhr.open('GET', url, true);
xhr.send();
}
It would work like this with nested callbacks and can be shuffled in any order that you would like.
request('request-a', function(dataA) {
// Request A has finished here.
// Now start request B.
// dataA is the xhr.responseText value.
request('request-b', function(dataB) {
// Request B has finished here.
// Now start request C.
request('request-c', function(dataC) {
// Request C has finished here.
});
});
});
This is the simplest way to make your request reusable and to act whenever your request has been finished. A more modern approach would be to use the Fetch API, which is a promise based interface that does the same thing as XMLHTTPRequest. Be sure to check it out.
You are looking for a system to make xhr requests synchronously. Try nested promises, where next request is made when previous promise resolves.
let promiseA = new Promise(function(resolve, reject) {
setTimeout(resolve, 100, 'foo');
});
promiseA.then((x) => {
// Launch request B
}).then((x) => {
// Launch request C
}).then((x) => {
// Launch request D....
});
There's a good explanation on that
Use fetch, promises and async await to make your life easier.
async function handlerA() {
log('Starting request A...')
let responseA = await fetch(urls.a)
let a = await responseA.text()
log('Starting request C...')
let responseC = await fetch(urls.c)
let c = await responseC.text()
log(a + c)
}
async function handlerB() {
log('Starting request B...')
let responseB = await fetch(urls.b)
let b = await responseB.text()
log(b)
}
const urls = {
a: 'https://httpbin.org/json',
b: 'https://httpbin.org/robots.txt',
c: 'https://httpbin.org/xml',
}
function log(str) {
document.getElementById('output').innerText += `${str}\n`
}
document.getElementById('btnA').addEventListener('click', handlerA)
document.getElementById('btnB').addEventListener('click', handlerB)
<button id="btnA">Button A</button>
<button id="btnB">Button B</button>
<div id="output"></div>
I found a solution that serves my need. Simply creates another XMLRequest object and use them in the if statement.
let requestA = new XMLHttpRequest();
let requestB = new XMLHttpRequest();
if(requestA.readyState === 4){
//Run request C
}
I'm attempting to execute a CORS request, then use async/await to get value out of it (I'm using jquery btw).
The functions createCORSRequest and executeCORSRequest both work fine I believe, so the implementation doesn't really matter. The function getDailyGames uses await to receive a promise created from executeCORSRequest, and then returns that promise.
My trouble is with actually using the promise in the $(document).ready function (this is basically the main function in jquery).
In the commented out line, the '.then' the method is used on the result of getDailyGames to print out the result of the function (this works fine).
However, I want to store the value of this promise in the 'result' variable and do stuff to it, but I can't get it out of the promise.
How would I correctly return this promise so that I can store the resolved value in a variable and do stuff with it?
Or another way to ask this: why does the 'result' variable in getDailyGames get a promise? Shouldn't use await with a promise give you the resolved value directly?
$(document).ready(function() {
//getDailyGames(20190313).then(result => console.log(result));
let result = getDailyGames(20190313);
//DO STUFF WITH RESULT
});
async function getDailyGames(date){
const url = 'https://api.jjjacobson.com/dailygames?season=2018-2019-
regular&date=' + date;
let result = await executeCORSRequest(url);
return result;
}
function executeCORSRequest(url){
return new Promise(function(resolve, reject) {
const xhr = createCORSRequest('GET', url);
if (!xhr) {
throw new Error('CORS not supported');
}
xhr.onload = function() {
resolve(xhr.responseText);
};
xhr.onerror = function() {
console.log('There was an error!');
reject('ERROR');
};
xhr.send();
});
}
function createCORSRequest(method, url) {
let xhr = new XMLHttpRequest();
if ("withCredentials" in xhr) {
// Check if the XMLHttpRequest object has a "withCredentials" property.
// "withCredentials" only exists on XMLHTTPRequest2 objects.
xhr.open(method, url, true);
}
else if (typeof XDomainRequest != "undefined") {
// Otherwise, check if XDomainRequest.
// XDomainRequest only exists in IE, and is IE's way of making CORS requests.
xhr = new XDomainRequest();
xhr.open(method, url);
}
else {
// Otherwise, CORS is not supported by the browser.
xhr = null;
}
return xhr;
}
You should change your document.ready function to:
$(document).ready(function() {
let result;
getDailyGames(20190313).then(res => {
result = res;
//DO STUFF WITH RESULT
});
});
Await works only when the function itself is async. You could also do this
$(document).ready(async function() {
let result = await getDailyGames(20190313);
//DO STUFF WITH RESULT
});
Async/await is only syntactical sugar aiming at eliminating the callback hell. It should be remembered well that somewhere down the asynchronous chain, you will have to deal with a promise. This is the reason why you can use await only inside async function and, sadly, the root of the script is not an async function.
In your case, if you want to serialize the call to getDailyGames, make the document.ready an async function as indicated by #varun.
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!!
I have a page that chains two API calls, loads the data into first_data and second_data before executing a createPage function (which is several kb of data manipulation and d3.js):
template.html
<script src="createPage.js"></script>
<script>
var first_data, second_data = [], [];
function getFirstData(){
return new Promise(function(resolve) {
var xhr = new XMLHttpRequest();
var url = "/API/my-request?format=json"
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
first_data = JSON.parse(xhr.responseText);
resolve('1');
}
}
xhr.open("GET", url, true);
xhr.send();
});
} //similar function for getSecondData()
getFirstData()
.then(getSecondData)
.then(createPage(first_data, second_data));
</script>
The trouble is that some of the code that manipulates the data in createPage is showing errors, for example "can't convert undefined to object". In that particular error's case, it's because I try to do Object.keys(data[0]) on some data that should be loaded from the API requests. Some observations:
If I inspect the data in the browser dev console, it's all there.
If I just paste the code from the file in the console, the page draws fine.
If I hard-code the initializing arrays etc for the data manipulation part of the code (to get rid of the can't convert undefined, then the page draws but all the graphics indicate that they were populated with no data.
The page loads fine if I put the the JSON data in a .js file and load it as a script just before the createPage.js file at the end of the body.
I inserted a console.log("starting") statement at the start and end of createPage(). Looking at the network and js console output when I load, the starting output occurs before the two API GET requests are displayed in the network activity. Is this representative of what's really happening (i.e. can you mix javascript console and network console timing?)
So, clearly I don't have access to the data at the point when I need it.
Why? Are my Promises incorrect?
How can I fix this?
Promise.prototype.then() expects 2 arguments(onFulfilled & onRejected) as function-expression(OR handler or callback) as it is a function(handler) which will be invoked when Promise is fulfilled
In your case, createPage(first_data, second_data) will invoke the function createPage when statement is interpreted by interpreter.
Use anonymous function as an argument and invoke your function inside it.
getFirstData()
.then(getSecondData)
.then(function() {
createPage(first_data, second_data);
});
Edit: If you are not passing any arguments specifically to the callback, you can use .then(FUNCTION_NAME)
In functional programming and using promises, you should probably refactor getFirstData (and getSecondData) to the following form:
function getFirstData(){
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest();
var url = "/API/my-request?format=json"
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
// Resolve the result, don't assign it elsewhere
resolve(JSON.parse(xhr.responseText));
} else {
// Add rejection state, don't keep the promise waiting
reject("XHR Error, status = ", xhr.status);
}
}
xhr.open("GET", url, true);
xhr.send();
});
}
And then resolve the promises like this (assume first & second data is not dependant on each other)
Promise.all([getFirstData(), getSecondData()]).then(function(allData){
var first_data = allData[0];
var second_data= allData[1];
return createPage(first_data, second_data);
}).catch(function(error){
console.log("Error caught: ", error);
});
To make things even cleaner, you can change createPages's from:
function createPage(first_data, second_data){
// Function body
}
to
function createPage(data){
var first_data = data[0];
var second_data= data[1];
// Function body
}
and the Promise part:
Promise.all([getFirstData(), getSecondData()]).then(createPage);
Notice how short it became?
Is there a way in JavaScript to send an HTTP request to an HTTP server and wait until the server responds with a reply? I want my program to wait until the server replies and not to execute any other command that is after this request. If the HTTP server is down I want the HTTP request to be repeated after a timeout until the server replies, and then the execution of the program can continue normally.
Any ideas?
Thank you in advance,
Thanasis
EDIT: Synchronous requests are now deprecated; you should always handle HTTP requests in an async way.
There is a 3rd parameter to XmlHttpRequest's open(), which aims to indicate that you want the request to by asynchronous (and so handle the response through an onreadystatechange handler).
So if you want it to be synchronous (i.e. wait for the answer), just specify false for this 3rd argument.
You may also want to set a limited timeout property for your request in this case, as it would block the page until reception.
Here is an all-in-one sample function for both sync and async:
function httpRequest(address, reqType, asyncProc) {
var req = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP");
if (asyncProc) {
req.onreadystatechange = function() {
if (this.readyState == 4) {
asyncProc(this);
}
};
}
req.open(reqType, address, !(!asyncProc));
req.send();
return req;
}
which you could call this way:
var req = httpRequest("http://example.com/aPageToTestForExistence.html", "HEAD"); // In this example you don't want to GET the full page contents
alert(req.status == 200 ? "found!" : "failed"); // We didn't provided an async proc so this will be executed after request completion only
You can perform a synchronous request. jQuery example:
$(function() {
$.ajax({
async: false,
// other parameters
});
});
You should take a look at jQuery's AJAX API. I highly recommend using a framework like jQuery for this stuff. Manually doing cross-browser ajax is a real pain!
You can use XMLHttpRequest object to send your request. Once request is sent, you can check readyState property to identify current state. readyState will have following different states.
Uninitialized - Has not started loading yet
Loading - Is loading
Interactive - Has loaded enough and the user can interact with it
Complete - Fully loaded
for example:
xmlhttp.open("GET","somepage.xml",true);
xmlhttp.onreadystatechange = checkData;
xmlhttp.send(null);
function checkData()
{
alert(xmlhttp.readyState);
}
hope this will help
For the modern browser, I will use the fetch instead of XMLHttpRequest.
async function job() {
const response = await fetch("https://api.ipify.org?format=json", {}) // type: Promise<Response>
if (!response.ok) {
throw Error(response.statusText)
}
return response.text()
}
async function onCommit() {
const result = await job()
// The following will run after the `job` is finished.
console.log(result)
}
fetch syntax
an examples
<button onclick="onCommit()">Commit</button>
<script>
function onCommit() {
new Promise((resolve, reject) => {
resolve(job1())
}).then(job1Result => {
return job2(job1Result)
}).then(job2Result => {
return job3(job2Result)
}).catch(err => { // If job1, job2, job3, any of them throw the error, then will catch it.
alert(err)
})
}
async function testFunc(url, options) {
// options: https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch
const response = await fetch(url, options) // type: Promise<Response>
if (!response.ok) {
const errMsg = await response.text()
throw Error(`${response.statusText} (${response.status}) | ${errMsg} `)
}
return response
}
async function job1() {
console.log("job1")
const response = await testFunc("https://api.ipify.org?format=json", {})
return await response.json()
}
async function job2(job1Data) {
console.log("job2")
console.log(job1Data)
const textHeaders = new Headers()
textHeaders.append('Content-Type', 'text/plain; charset-utf-8')
const options = {"headers": textHeaders}
const response = await testFunc("https://api.ipify.org/?format=text", options)
// throw Error(`test error`) // You can cancel the comment to trigger the error.
return await response.text()
}
function job3(job2Data) {
console.log("job3")
console.log(job2Data)
}
</script>
For this you can start loader in javascript as soon as page starts loading and then you can close it when request finishes or your dom is ready.
What i am trying to say, as page load starts, start a loader . Then page can do multiple synchronous request using ajax , until and unless you didn't get response, do not close close loader.
After receiving the desired in response in final call, you can close the loader.
I have a similar situation in an game built with Three.js and Google Closure. I have to load 2 resources, Three and Closure do not allow me to make these synchronous.
Initially I naively wrote the following:
main() {
...
var loaded=0;
...
// Load Three geometry
var loader = new THREE.JSONLoader();
loader.load("x/data.three.json", function(geometry) {
...
loaded++;
});
// Load my engine data
goog.net.XhrIo.send("x/data.engine.json", function(e) {
var obj = e.target.getResponseJson();
...
loaded++;
});
// Wait for callbacks to complete
while(loaded<2) {}
// Initiate an animation loop
...
};
The loop that waits for the callbacks to complete never ends, from the point of view of the loop loaded never get incremented. The problem is that the callbacks are not fired until main returns (at least on Chrome anyway).
One solution might be to have both callbacks check to see if it's the last to complete, and them go on to initiate the animation loop.
Another solution - perhaps a more direct answer to what you are asking (how to wait for each load before initiating another) - would be to nest the callbacks as follows:
// Load Three geometry
var loader = new THREE.JSONLoader();
loader.load("x/data.three.json", function(geometry) {
...
// Load my engine data
goog.net.XhrIo.send("x/data.engine.json", function(e) {
var obj = e.target.getResponseJson();
...
// Initiate an animation loop
...
});
});
};
This is an old question but wanted to provide a different take.
This is an async function that creates a promise that resolves with the Http object when the request is complete. This allow you to use more modern async/await syntax when working with XMLHttpRequest.
async sendRequest() {
const Http = new XMLHttpRequest();
const url='http://localhost:8000/';
Http.open("GET", url);
Http.send();
if (Http.readyState === XMLHttpRequest.DONE) {
return Http;
}
let res;
const p = new Promise((r) => res = r);
Http.onreadystatechange = () => {
if (Http.readyState === XMLHttpRequest.DONE) {
res(Http);
}
}
return p;
}
Usage
const response = await sendRequest();
const status = response.status;
if (status === 0 || (status >= 200 && status < 400)) {
// The request has been completed successfully
console.log(response.responseText);
} else {
// Oh no! There has been an error with the request!
console.log(`Server Error: ${response.status}`)
}
For those using axios, you can wrap it in an async iife and then await it:
(async () => {
let res = await axios.get('https://example.com');
// do stuff with the response
})();
Note, I haven't done any error checking here.