I am kinda new to Javascript and have written some Chrome Extension. The code contains a sequence process where one function is being passed to another and will be called when the first is done. To be frank, this is getting complicated for me to even look at.
I will explain with an example that shows the sequence I have in my code:
function successFunc() {
console.log("Success!");
}
function handleErrorFunc(successFunc) {
... stuff to handle error...
step1(successFunc);
}
function step1(successFunc) {
var url = ...
var request = new XMLHttpRequest();
request.open("GET", url, true);
request.send();
request.onerror = function() {
...
}
request.onreadystatechange = function() {
if (request.readyState == 4 && request.status == 200) {
...
step2(successFunc)
}
}
}
function step2(successFunc) {
var url = ...
var request = new XMLHttpRequest();
request.open("GET", url, true);
request.send();
request.onerror = function() {
...
}
request.onreadystatechange = function() {
if (request.readyState == 4 && request.status == 200) {
...
successFunc()
}
}
}
Now I call
step1(successFunc);
Is there something to prevent this from happening, some design pattern perhaps?
Many javascript libraries return a Promise from an ajax call. This is a preferable method to using callbacks as it neatens your code considerable.
Instead of the code you currently have, your code would instead look like this.
step1().then(step1Result => step2(step1Result))
.catch(err => console.error("step1 faled",err));
You can continue to chain this as appropriate
step1().then(step1Result => step2(step1Result)
.then(step2Result => step3(step2Result))
.catch(err => console.error("step2 failed",err))
)
.catch(err => console.error("step1 faled",err));
Using the standard XMLHttpRequest does not follow this pattern, but it is straightforward to wrap in a Promise if thats what you want to do - however it is much easier to use an existing ajax library which supports promises.
I would suggest you start by reading Using Promises guide.
edit: If you want to wrap your calls in an Promise make sure you pass back the response as well as the errors. eg:
function ajaxGet(url){
return new Promise((resolve, reject) => {
var request = new XMLHttpRequest();
request.open("GET", url, true);
request.send();
request.onerror = reject; // will pass back error
request.onreadystatechange = function() {
if (request.readyState == XMLHttpRequest.DONE && request.status == 200) {
resolve(request.responseText);
}
}
});
}
Then step1 might be
function step1(){
return ajaxGet("url/for/step1");
}
What you want to look at is promises in Javascript. But before you dive into something that specific I would recommend you get a basic understanding of functional programming in Javascript.
A few links to help you out in this case are given below -
Functional Programming in JS
Promises in JS
A better approach to your problem is to use promises :
function successFunc() {
console.log("Success!");
}
// now returns a promise and does not need successFunc
function step1() {
return new Promise((resolve, reject) => {
const request = new XMLHttpRequest();
request.open("GET", 'your/url', true);
request.send();
request.onerror = function() {
reject();
}
request.onreadystatechange = function() {
if (request.readyState == 4 && request.status == 200) {
resolve();
}
}
});
}
// now returns a promise and does not need successFunc
function step2() {
return new Promise((resolve, reject) => {
var request = new XMLHttpRequest();
request.open("GET", 'your/url', true);
request.send();
request.onerror = function() {
reject();
}
request.onreadystatechange = function() {
if (request.readyState == 4 && request.status == 200) {
resolve();
}
}
});
}
function handleErrorFunc(step) {
if (step === 1) {
// handle step1 errors...
}
else if (step === 2) {
// handle step2 errors...
}
}
// chain your step calls
step1()
.then(() => {
step2()
.then(() => {
successFunc();
})
.catch(() => {
handleErrorFunc(2);
});
})
.catch(() => {
handleErrorFunc(1);
});
Related
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()
I am using Async XMLHttpRequest to make an API call. Here's the workflow of my program,
first_function(){
var valueToBeReturned = 0;
makeRequest(myCallback)//function for API call
/*rest of the code*/
console.log("print second");
return valueToBeReturned;
}
function makeRequest(callback){
var xhttp = new XMLHttpRequest();
xhttp.open("GET", "my_url", true);
xhttp.send(null);
xhttp.onload = function() {
if(xhttp.readyState === 4) {
if(xhttp.status === 200) {
response = JSON.parse(xhttp.responseText);
callback(null, response);
}
}
}
}
function myCallback(data){
console.log("print first")
}
Now what happens is every time I run it, the entire code in the first function is executed and then the code in makeRequest is executed. I understand JS is synchronous in nature and everything. But I'm not able to get my work done here, which is fisrt it makes API call, then callback is executed, then the code after makeRequest. What am I doing wrong here?
PS this is not the actual code, just to demonstrate the flow of my program
You need to put callback as a parameter in makeRequest. I'm not sure what that null is there for, though. If you want "print second" to print second, you'll need to execute it after myCallback - maybe insert another callback?
function first_function(){
var valueToBeReturned = 0;
makeRequest(myCallback, restOfTheCode)
function restOfTheCode() {
/*rest of the code*/
console.log("print second");
}
}
function makeRequest(callback1, callback2){
var xhttp = new XMLHttpRequest();
xhttp.open("GET", "my_url", true);
xhttp.send(null);
xhttp.onload = function() {
if(xhttp.readyState === 4 && xhttp.status === 200) {
const response = JSON.parse(xhttp.responseText);
callback1(response);
callback2(response);
}
}
}
function myCallback(data){
console.log("print first");
}
But this whole thing would be a whole lot nicer if you used Fetch and Promises instead:
function makeRequest() {
return fetch('my_url')
.then(response => response.JSON())
}
// consume the request:
makeRequest()
.then(responseJSON => {
// do stuff with the responseJSON
});
Full disclosure: I'd qualify myself as having intermediate JavaScript knowledge. So this is slightly above my experience level at this time.
I've got a Google Chrome Extension that does an AJAX request for a local file:/// as soon as a page loads. After I get the response back from the request I use the returned code in several functions later on in my code. Most of the time I get the response back before my code that needs it runs. But sometimes I don't and everything breaks.
Now, I assume I could just throw all of the relevant code inside of the xhr.onload below. But that seems really inefficient? I have a lot of moving parts that rely on the response and it seems bad to put them all in there.
I've perused several articles related to async/await and I'm having trouble grasping the concept. I'm also not 100% positive I'm looking at this the right way. Should I even be considering using async/await?
Here is the code for my AJAX request.
var xhr = new XMLHttpRequest();
xhr.open("GET", url, true);
xhr.onload = function(e) {
code = xhr.response;
};
xhr.onerror = function () {
console.error("** An error occurred during the XMLHttpRequest");
};
xhr.send();
Let's say I've got a bunch of functions that need to fire afterwards later on in my code. Right now they just look like:
function doTheThing(code) {
// I hope the response is ready.
}
What's the best way to approach this? FYI, the Fetch API isn't an option.
Here's a high level view of how my code is structured.
// AJAX request begins.
// ...
// A whole bunch of synchronous code that isn't dependant on
// the results of my AJAX request. (eg. Creating and appending
// some new DOM nodes, calculating some variables) I don't want
// to wait for the AJAX response when I could be building this stuff instead.
// ...
// Some synchronous code that is dependant on both my AJAX
// request and the previous synchronous code being complete.
// ...
// Some more synchronous code that needs the above line to
// be complete.
I usually do async/await like this:
async function doAjaxThings() {
// await code here
let result = await makeRequest("GET", url);
// code below here will only execute when await makeRequest() finished loading
console.log(result);
}
document.addEventListener("DOMContentLoaded", function () {
doAjaxThings();
// create and manipulate your DOM here. doAjaxThings() will run asynchronously and not block your DOM rendering
document.createElement("...");
document.getElementById("...").addEventListener(...);
});
Promisified xhr function here:
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();
});
}
I create a promise for the XHR. Then simply use await inside an async function to call it.
function getHTML(url) {
return new Promise(function (resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.open('get', url, true);
xhr.responseType = 'document';
xhr.onload = function () {
var status = xhr.status;
if (status == 200) {
resolve(xhr.response.documentElement.innerHTML);
} else {
reject(status);
}
};
xhr.send();
});
}
async function schemaPageHandler(){
try {
var parser = new window.DOMParser();
var remoteCode = await getHTML('https://schema.org/docs/full.html');
var sourceDoc = parser.parseFromString(remoteCode, 'text/html');
var thingList = sourceDoc.getElementById("C.Thing");
document.getElementById("structured-data-types").appendChild(thingList);
} catch(error) {
console.log("Error fetching remote HTML: ", error);
}
}
You get two options,
first is to use newer fetch api which is promise based, with with you can do
let response = await fetch(url);
response = await response.json();; // or text etc..
// do what you wanna do with response
Other option if you really want to use XMLHttpRequest is to promisify it
let response = await new Promise(resolve => {
var xhr = new XMLHttpRequest();
xhr.open("GET", url, true);
xhr.onload = function(e) {
resolve(xhr.response);
};
xhr.onerror = function () {
resolve(undefined);
console.error("** An error occurred during the XMLHttpRequest");
};
xhr.send();
})
// do what you wanna do with response
possible full solution
(async () => {
let response = await new Promise(resolve => {
var xhr = new XMLHttpRequest();
xhr.open("GET", url, true);
xhr.onload = function(e) {
resolve(xhr.response);
};
xhr.onerror = function () {
resolve(undefined);
console.error("** An error occurred during the XMLHttpRequest");
};
xhr.send();
})
doTheThing(response)
})()
I had the same problem and solved it using the following function:
const makeRequest = (method, url, data = {}) => {
const xhr = new XMLHttpRequest();
return new Promise(resolve => {
xhr.open(method, url, true);
xhr.onload = () => resolve({
status: xhr.status,
response: xhr.responseText
});
xhr.onerror = () => resolve({
status: xhr.status,
response: xhr.responseText
});
if (method != 'GET') xhr.setRequestHeader('Content-Type', 'application/json');
data != {} ? xhr.send(JSON.stringify(data)) : xhr.send();
})
}
const test = async() => {
console.log("Starting request ...")
let request = await makeRequest("GET", "https://jsonplaceholder.typicode.com/todos/1");
console.log("status:", request.status)
console.log("response:", request.response)
}
test()
You can for example create an asynchronous class to use instead of the original one. It lacks some methods but it can serve as an example.
(function() {
"use strict";
var xhr = Symbol();
class XMLHttpRequestAsync {
constructor() {
this[xhr] = new XMLHttpRequest();
}
open(method, url, username, password) {
this[xhr].open(method, url, true, username, password);
}
send(data) {
var sxhr = this[xhr];
return new Promise(function(resolve, reject) {
var errorCallback;
var loadCallback;
function cleanup() {
sxhr.removeEventListener("load", loadCallback);
sxhr.removeEventListener("error", errorCallback);
}
errorCallback = function(err) {
cleanup();
reject(err);
};
loadCallback = function() {
resolve(xhr.response);
};
sxhr.addEventListener("load", loadCallback);
sxhr.addEventListener("error", errorCallback);
sxhr.addEventListener("load", function load() {
sxhr.removeEventListener("load", load);
resolve(sxhr.response);
});
sxhr.send(data);
});
}
set responseType(value)
{
this[xhr].responseType = value;
}
setRequestHeader(header, value) {
this[xhr].setRequestHeader(header, value);
}
}
addEventListener("load", async function main() {
removeEventListener("load", main);
var xhra = new XMLHttpRequestAsync();
xhra.responseType = "json";
xhra.open("GET", "appserver/main.php/" + window.location.hash.substring(1));
console.log(await xhra.send(null));
});
}());
I have a function that loads my html templates asynchronously:
loadTplAsync: function(path) {
return Q.Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.open("GET", path, true);
xhr.onload = () => {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
resolve(_.template(xhr.responseText));
} else {
reject(xhr.responseText);
}
}
};
xhr.onerror = error => reject(error);
xhr.send(null);
});
}
How to extend this function to cache responses by browser?
Assuming that what you mean by cache is not to repeat making same request during life cycle of that page load you could store the promise as a variable and return the same promise each time.
The first time a specific path is requested will make a new request, subsequent times only the stored promise will be returned
var promises ={};
loadTplAsync: function(path) {
// create new promise if it doesn't already exist for path instance
if(!promises[path]){
promises[path] = Q.Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.open("GET", path, true);
xhr.onload = () => {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
resolve(_.template(xhr.responseText));
} else {
reject(xhr.responseText);
}
}
};
xhr.onerror = error => reject(error);
xhr.send(null);
});
}
// return the stored promise
return promises[path];
}
Note this is not a persistent cache and new requests would be made on subsequent page loads
I'm writing webpage with a javascript to read data files in text format from the server per user request. Once the text file has been loaded, I need to manipulate the data somewhat.
I have been using XMLHttpRequest for the loading, however, now I see that synchronous requests are "deprecated". I can't start manipulating the data before it's loaded, so what can I do in this case?
Use an asynchronous request (or fetch, see below, which is also asynchronous):
function doGET(path, callback) {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
// The request is done; did it work?
if (xhr.status == 200) {
// ***Yes, use `xhr.responseText` here***
callback(xhr.responseText);
} else {
// ***No, tell the callback the call failed***
callback(null);
}
}
};
xhr.open("GET", path);
xhr.send();
}
function handleFileData(fileData) {
if (!fileData) {
// Show error
return;
}
// Use the file data
}
// Do the request
doGET("/path/to/file", handleFileData);
Or using promises, which are the more modern way to handle callbacks (but keep reading):
function doGET(path, callback) {
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
// The request is done; did it work?
if (xhr.status == 200) {
// Yes, use `xhr.responseText` to resolve the promise
resolve(xhr.responseText);
} else {
// No, reject the promise
reject(xhr);
}
}
};
xhr.open("GET", path);
xhr.send();
});
}
// Do the request
doGET("/path/to/file")
.then(function(fileData) {
// Use the file data
})
.catch(function(xhr) {
// The call failed, look at `xhr` for details
});
Here in 2019, there's no reason to use XHR wrapped in a promise like that, just use fetch:
function doGET(url) {
return fetch(url).then(response => {
if (!response.ok) {
throw new Error("HTTP error " + response.status); // Rejects the promise
}
});
}
Since you want to handle the local file, Try this
Make use of XMLHttpRequest
function readFile(file)
{
var f = new XMLHttpRequest();
f.open("GET", file, false);
f.onreadystatechange = function ()
{
if(f.readyState === 4)
{
if(f.status === 200 || f.status == 0)
{
var res= f.responseText;
alert(res);
}
}
}
f.send(null);
}
Then you have to call with File:\\
readFile('File:\\\yourpath');