Await javascript recursive function to return true - javascript

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!!

Related

How to make AJAX requests synchronous without jQuery?

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

working with Node js Await/Async functions

This is my axios Request to call the API.
export function axiosGet (url) {
return opsutils.get(url)
.then(function (response) {
return response.data.data;
})
.catch(function (error) {
return 'An error occured..' + error;
})
}
From here i'm calling it asynchrously
async getDirList(data){
this.domainDir=data.domain_name
var apiurl="some URL"
var stat = await axiosGet(apiurl)
if (status){
this.domainlog= stat
}
From here i'm calling the async func defined above
Notify(data){
var filelist = this.getDirList(data)
if(filelist){
var status = createNotification(data.domain_name,message_stripped,'Degrading web server performance')
}
The ideal should be like this that it should go forward only after the promise is resolved ,right now the var filelist got empty.
How do i get to solve this problem ?Thanks in advance
The problem is this.getDirList(data) is not being accessed asynchronously as well. Remember, because that is async now, it's returning a promise, so you either need to chain it with .then():
Notify(data){
var filelist = this.getDirList(data)
.then(data => {
var status = createNotification(data.domain_name,message_stripped,'Degrading web server performance')
});
}
Or turn Notify() into an async function as well:
async Notify(data){
var filelist = await this.getDirList(data);
if(filelist){
var status = createNotification(data.domain_name,message_stripped,'Degrading web server performance')
}
Additionally, make sure you're actually returning data from getDirList so you can utilize the return value when you await it.

Async/Await not waiting for response, and returns Promise object

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.

Using chrome.tabs.executeScript to execute an async function

I have a function I want to execute in the page using chrome.tabs.executeScript, running from a browser action popup. The permissions are set up correctly and it works fine with a synchronous callback:
chrome.tabs.executeScript(
tab.id,
{ code: `(function() {
// Do lots of things
return true;
})()` },
r => console.log(r[0])); // Logs true
The problem is that the function I want to call goes through several callbacks, so I want to use async and await:
chrome.tabs.executeScript(
tab.id,
{ code: `(async function() {
// Do lots of things with await
return true;
})()` },
async r => {
console.log(r); // Logs array with single value [Object]
console.log(await r[0]); // Logs empty Object {}
});
The problem is that the callback result r. It should be an array of script results, so I expect r[0] to be a promise that resolves when the script finishes.
Promise syntax (using .then()) doesn't work either.
If I execute the exact same function in the page it returns a promise as expected and can be awaited.
Any idea what I'm doing wrong and is there any way around it?
The problem is that events and native objects are not directly available between the page and the extension. Essentially you get a serialised copy, something like you will if you do JSON.parse(JSON.stringify(obj)).
This means some native objects (for instance new Error or new Promise) will be emptied (become {}), events are lost and no implementation of promise can work across the boundary.
The solution is to use chrome.runtime.sendMessage to return the message in the script, and chrome.runtime.onMessage.addListener in popup.js to listen for it:
chrome.tabs.executeScript(
tab.id,
{ code: `(async function() {
// Do lots of things with await
let result = true;
chrome.runtime.sendMessage(result, function (response) {
console.log(response); // Logs 'true'
});
})()` },
async emptyPromise => {
// Create a promise that resolves when chrome.runtime.onMessage fires
const message = new Promise(resolve => {
const listener = request => {
chrome.runtime.onMessage.removeListener(listener);
resolve(request);
};
chrome.runtime.onMessage.addListener(listener);
});
const result = await message;
console.log(result); // Logs true
});
I've extended this into a function chrome.tabs.executeAsyncFunction (as part of chrome-extension-async, which 'promisifies' the whole API):
function setupDetails(action, id) {
// Wrap the async function in an await and a runtime.sendMessage with the result
// This should always call runtime.sendMessage, even if an error is thrown
const wrapAsyncSendMessage = action =>
`(async function () {
const result = { asyncFuncID: '${id}' };
try {
result.content = await (${action})();
}
catch(x) {
// Make an explicit copy of the Error properties
result.error = {
message: x.message,
arguments: x.arguments,
type: x.type,
name: x.name,
stack: x.stack
};
}
finally {
// Always call sendMessage, as without it this might loop forever
chrome.runtime.sendMessage(result);
}
})()`;
// Apply this wrapper to the code passed
let execArgs = {};
if (typeof action === 'function' || typeof action === 'string')
// Passed a function or string, wrap it directly
execArgs.code = wrapAsyncSendMessage(action);
else if (action.code) {
// Passed details object https://developer.chrome.com/extensions/tabs#method-executeScript
execArgs = action;
execArgs.code = wrapAsyncSendMessage(action.code);
}
else if (action.file)
throw new Error(`Cannot execute ${action.file}. File based execute scripts are not supported.`);
else
throw new Error(`Cannot execute ${JSON.stringify(action)}, it must be a function, string, or have a code property.`);
return execArgs;
}
function promisifyRuntimeMessage(id) {
// We don't have a reject because the finally in the script wrapper should ensure this always gets called.
return new Promise(resolve => {
const listener = request => {
// Check that the message sent is intended for this listener
if (request && request.asyncFuncID === id) {
// Remove this listener
chrome.runtime.onMessage.removeListener(listener);
resolve(request);
}
// Return false as we don't want to keep this channel open https://developer.chrome.com/extensions/runtime#event-onMessage
return false;
};
chrome.runtime.onMessage.addListener(listener);
});
}
chrome.tabs.executeAsyncFunction = async function (tab, action) {
// Generate a random 4-char key to avoid clashes if called multiple times
const id = Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
const details = setupDetails(action, id);
const message = promisifyRuntimeMessage(id);
// This will return a serialised promise, which will be broken
await chrome.tabs.executeScript(tab, details);
// Wait until we have the result message
const { content, error } = await message;
if (error)
throw new Error(`Error thrown in execution script: ${error.message}.
Stack: ${error.stack}`)
return content;
}
This executeAsyncFunction can then be called like this:
const result = await chrome.tabs.executeAsyncFunction(
tab.id,
// Async function to execute in the page
async function() {
// Do lots of things with await
return true;
});
This wraps the chrome.tabs.executeScript and chrome.runtime.onMessage.addListener, and wraps the script in a try-finally before calling chrome.runtime.sendMessage to resolve the promise.
Passing promises from page to content script doesn't work, the solution is to use chrome.runtime.sendMessage and to send only simple data between two worlds eg.:
function doSomethingOnPage(data) {
fetch(data.url).then(...).then(result => chrome.runtime.sendMessage(result));
}
let data = JSON.stringify(someHash);
chrome.tabs.executeScript(tab.id, { code: `(${doSomethingOnPage})(${data})` }, () => {
new Promise(resolve => {
chrome.runtime.onMessage.addListener(function listener(result) {
chrome.runtime.onMessage.removeListener(listener);
resolve(result);
});
}).then(result => {
// we have received result here.
// note: async/await are possible but not mandatory for this to work
logger.error(result);
}
});
For anyone who is reading this but using the new manifest version 3 (MV3), note that this should now be supported.
chrome.tabs.executeScript has been replaced by chrome.scripting.executeScript, and the docs explicitly state that "If the [injected] script evaluates to a promise, the browser will wait for the promise to settle and return the resulting value."

How to force a program to wait until an HTTP request is finished in JavaScript?

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.

Categories