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();
})()
Related
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?
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
}
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 one problem
Here is my custom command
client.addCommand('getBinaryImage', function (message) {
var self = this;
return self.execute(
function downloadImageBinary(url) {
var err = null;
var result = null;
function toDataURL(url, callback) {
var xhr = new XMLHttpRequest();
xhr.onload = function () {
var reader = new FileReader();
reader.onloadend = function () {
callback(reader.result);
};
reader.readAsDataURL(xhr.response);
};
xhr.open('GET', url);
xhr.responseType = 'blob';
xhr.send();
}
toDataURL(url, function (dataUrl) {
alert('RESULT:' + dataUrl);
// Now return the result !!
});
}
);
});
And I am using it as follows
let chain = client
.url("https://stackoverflow")
.getBinaryImage("image url here")
.then((result) => {
console.log(result);
})
But as you can see the execution of the script on the browsers side is async.
The problem is that I need to execute this script sync in the chain order, so until the result is not ready it should not reach the then handler in the chain.
Please suggest how to implement this ?
You need to return a promise that resolves only when you get your data back.
I'm using http://bluebirdjs.com as the promises library, they also have a nice and simple example over there - http://bluebirdjs.com/docs/api/new-promise.html
wrap your entire function with
return new Promise(function (resolve, reject) {
and when you get the result (your // Now return the result !! code) invoke resolve(dataUrl)
I am overriding a javascript function like this :
(function() {
origFunc = aFunction;
aFunction = function() {
doRequest();
//return origFunc.apply(this);
};
})();
I know that I need to end the function with "return origFunc.apply(this)" in order to execute the original function. However, as I'm executing a request, I have to wait until the request is done. That's why I wrote this function :
doRequest: function()
{
try
{
if(window.XMLHttpRequest)
httpRequest = new XMLHttpRequest();
else if(window.ActiveXObject)
httpRequest = new ActiveXObject("Microsoft.XMLHTTP");
var url = anUrl, self = this;
httpRequest.onreadystatechange = function(data)
{
try
{
if(httpRequest.readyState == 4)
{
if(httpRequest.status == 200)
return origFunc.apply(self);
else if(httpRequest.status != 0 )
alert("Error while executing the request : "+httpRequest.status+"\r\nUrl : "+url);
}
}
catch(e)
{
}
};
httpRequest.open("GET", url);
httpRequest.send();
}
catch(err)
{
alert("Error : "+err);
}
}
As you can guess, the problem is that I can't do the things like that.
Do you know how I could do ?
Here is an example for how to deal with wrapping async functions
// This simply calls the callback with some data after a second
// Could be an AJAX call for example
var doSomethingAsync = function (callback) {
setTimeout(function () {
callback({ some: 'data' });
}, 1000);
};
var fnThatMakesAsyncCall = function () {
// From the outside there is no way to change this callback
// But what if we need to intercept the async function to change the data given to it, or monitor it?
// Then we'd have to wrap the async function to wrap the callback.
var callback = function (data) {
console.log('Original', data);
};
doSomethingAsync(callback);
};
// Function to wrap another function and return the wrapper
var wrapFn = function (fn) {
// Create the wrapped function.
// Notice how it has the same signature with `callback` as the first argument
var wrapped = function (callback) {
// Here we get the original callback passed in
// We will instead wrap that too and call the original function with our new callback
var newCb = function (data) {
// This will run once the async call is complete
// We will as an example mutate the data in the return data of the callback
data.some = 'Wrapped it';
// Call the original callback with the changed data
callback.call(this, data);
};
// Run the function we wrap with the new callback we supply
fn.call(this, newCb);
};
// Return wrapped function
return wrapped;
};
// Will log Original {some: "data"}
fnThatMakesAsyncCall();
doSomethingAsync = wrapFn(doSomethingAsync);
// Will log Original {some: "Wrapped it"}
fnThatMakesAsyncCall();