I have implemented an async function which creates an XHR object inside a Promise, requests a data from the server and fulfills the Promise with the response received from the server
async function playWordsAudio(id, type, check=false){
let file_namePromise = new Promise(function (onSuccess, onFailure){
var xhttp = new XMLHttpRequest();
xhttp.onload = function(){
if(this.readyState == 4 && this.status == 200){
if(this.responseText !== "No Data"){
file_name = this.responseText;
if(check == false){
audio = new Audio('assets/uploads/wav/' + file_name);
audio.play();
}else{
onSuccess(file_name);
}
}else{
if(check){
onSuccess('Not Found');
}
}
}
};
xhttp.open("GET", "scripts/get_word_audio.php?id=" + id + "&type=" + type, true);
xhttp.send();
});
let resultant = await file_namePromise;
if(check){
return resultant;
}
}
Later I have defined another function which calls the async function shown above
function modify_object(id, type, obj){
result = playWordsAudio(id, type, true);
result.then(function(value){
if(value == "Not Found"){
obj.classList.remove('fa-play');
}
});
}
Later in the implementation I also call the modify_object function and pass it an object for modification as described in the function.
modify_object(id, type, plays[i]);
In the async function I have created a Promise, on success I pass it the file_name received from the XHR response or else I pass it 'Not Found'.
Here I am confused on how the await part works inside the asyc function.
It returns the resultant to the caller of the async function which is
result = playWordsAudio(id, type, true);
If so, what does the onSuccess method in the Promise do
let file_namePromise = new Promise(function (onSuccess, onFailure){
/*
Some Codes
*/
onSuccess(file_name);
/*
Some more code
*/
onSuccess('Not Found);
/*
Rest of the Code
*/
}
As I have checked by commenting out the return statement after the await statement
if(check){
// return resultant;
}
The Promise is not fulfilled.
How does each part of the implementation work as described above?
Related
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();
})()
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()
while (repoUrlLink != null && count < 90) {
var xmlHttp = new XMLHttpRequest();
xmlHttp.open('GET', repoUrlLink, false);
xmlHttp.setRequestHeader('Authorization', 'Bearer ' + userData.accessToken);
xmlHttp.onload = function () {
if (xmlHttp.status != 200) {
displayNoAccessMessage();
break;
}
var result = JSON.parse(xmlHttp.responseText);
if (result.length == 0) {
displayNoRecordsMessage();
break;
}
var header = xmlHttp.getResponseHeader('link');
if (header) {
//doing something
}
else
repoUrlLink = null;
$.each(result, function (index, eachData) {
// doing something with data
count++;
});
}
xmlHttp.send();
}
Is there any better way to come out of the loop as soon as i display error.The break statement is not working. Is there any callback which can be useful ?
As someone said in the comments, you'll want to use recursion. This is because xmlHttp is an asynchronous operation. When you call send, the browser will send off the request, then continue on with the code, so by the time the onload function is called, it's no longer valid to break out of the loop. Also, being a function, onload is in no position to call break, since the while loop isn't even in the same scope.
var count = 0;
var doXmlHttpCall = function() {
var xmlHttp = new XMLHttpRequest();
// additional setup code here
xmlHttp.onload = function() {
var errorFound = false;
if (/* some error case */) {
errorFound = true;
}
// process request
if (count < 90 && !errorFound) {
doXmlHttpCall();
}
}
}
doXmlHttpCall();
Some ideas to refactor the code using promises.
a promisified XMLHttpRequest request function getRepLink that performs one request. It rejects for request errors and HTTP errors (not "200" status).
a promisifed getSetOfRecords function to get a single response, parse it as JSON data and extract the link header value. It rejects iff there are no records.
a promisified process records function which tries to process a given number of records in sets. It fulfills with the number of records processed. It ignores the no records error if some records have already been processed.
// XMLHttp request
function getRepoLink (repUrlLink) {
return new Promise( function (resolve, reject) {
var xmlHttp = new XMLHttpRequest();
xmlHttp.open('GET', repoUrlLink, false);
xmlHttp.setRequestHeader('Authorization', 'Bearer ' + userData.accessToken);
xmlHttp.onload = function () {
if( xmlHttp.status == 200) {
resolve( xmlHttp);
}
else {
reject( new Error("HTTP error " + xmlHttp.status));
}
};
xmlHttp.onerror = reject;
xmlHttp.send();
});
}
// get a set of records
const EndOfRecords = new Error( "End of Records");
function getSetOfRecords( repUrlLink) {
return getRepoLink( repUrlLink).then(
function( xmlHttp) {
var result = JSON.parse(xmlHttp.responseText);
if (result.length == 0) {
displayNoRecordsMessage();
throw EndOfRecords; // reject the returned promise
}
var header = xmlHttp.getResponseHeader('link');
return {result, header}; // fulfill the returned promise
}
);
}
// get up to `count` records and process them
function processRecords( repUrlLink, count) {
var processed = 0;
function processSomeMore() {
return getSetOfRecords().then( function ({result, header}) {
$.each(result, function (index, eachData) {
// do something with data
processed++;
});
if( header) {
//do something with header
if( processed < count)
return processSomeMore() // asynchronous "recursion"
}
else {
// do as required if no link header present.
}
return processed; // fulfill returned promise
},
function( error) {
if( error === EndOfRecords && processed > 0) {
return processed; // got some records
};
throw error; // reject returned promise
});
}
return processSomeMore();
}
// Start asynchronous operation
processRecords( repUrlLink, 90)
.then( processed => console.log( processed + " records processed"))
.catch( error => console.log( error));
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');
I'm trying to chain some API calls before setting the textContent of some spans in my webpage. I can execute the following ajax API calls separately by pasting them into the console, but when I chain them as promises I get getFirstData() is undefined.
var first_data = [],
second_data = [];
function getFirstData(){
var xhr = new XMLHttpRequest();
var url = "/API/first-data?format=json"
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
first_data = JSON.parse(xhr.responseText);
return Promise.resolve('1');
}
}
xhr.open("GET", url, true);
xhr.send();
}
/*getSecondData is the same, but with a different API url. I'll DRY these
two into one function that takes a url argument when I get it working.*/
getFirstData().then(getSecondData).then(createPage);
This is between <script> tags just before </body>. So what's wrong with the call to getFirstData() on the last line that causes the interpreter to say it's undefined? For reference, in the network log, getSecondData() is sent and returns just fine.
(Note: I'm specifically trying to do this without JQuery).
The issue occurs because your function is returning undefined (in other words, getting to the end of the function block before it returns) before it ever gets a chance to return Promise.resolve('1').
Your function has to immediately return a Promise object, which becomes pending before eventually resolving inside your AJAX handler.
I'd also add error handling using the provided reject argument, as is standard for Promise objects.
function getFirstData(){
return new Promise(function(resolve, reject) { // encapsulate code in a promise which returns immediately
var xhr = new XMLHttpRequest();
var url = "/echo/json"
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
first_data = JSON.parse(xhr.responseText);
return resolve('1');
}
else {
return reject('There was an error!') // reject the promise if error occurs
}
}
xhr.open("GET", url, true);
xhr.send();
});
}
And then catch it in the thenable chain:
getFirstData()
.then(getSecondData)
.catch(function(err){Throw err}) // catch the error if it throws
.then(createPage);
See working jsfiddle
getFirstData isn't returning a promise it returns undefined, which is not thenable.
function getFirstData(){
return new Promise(function(resolve) {
var xhr = new XMLHttpRequest();
var url = "/API/first-data?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();
});
}