javascript async await not waiting - javascript

Here are the functions I have.
async function check_saved_url() {
$.get("https://ipinfo.io/json", function (response) {
current_ip = response.ip;
location_here = response.country;
var ajax_get_url_prefix = base_url + 'getDomain.php?ip=';
var key_ip = current_ip + '###' + domain_permanent;
var url = ajax_get_url_prefix + key_ip;
$.get(url, function(data, status) {
console.log("Data: " + data + "\nStatus: " + status);
if (data.includes('0 results')) {
return 'unknown';
} else {
return data;
}
});
}, "jsonp");
}
const func1 = async() => {
return await check_saved_url();
}
const func2 = async() => {
let domain_preference = '';
domain_preference = await func1();
console.log("domain_preference: ",domain_preference);
}
func2();
This method is from this answer.
As you can see, there are two jquery ajax to get data from server.
The problem is, the func1 and func2 never waits until check_saved_url returns value.
This is what I see in console.
The top red line is the output of func2, which must wait until check_saved_url runs, whose result is the following 2 circles.
I am not sure why this persists to happen and hours of copying answers from everywhere didn't help me.

You probably need to develop your understanding of promises and how async JavaScript works a little.
If you have to use jQuery, you could promisify the jQuery $.ajax method (which appears to use a very old version of the promise API?), and then use that with modern JS constructs like async/await.
const pGet = (url, dataType = 'jsonp') =>
new Promise((success, error) => $.ajax({url, dataType, success, error}))
const IP_URL = 'https://ipinfo.io/json'
const PREFERENCE_URL_PREFIX = 'https://www.example.com/getDomain.php?ip='
const go = async () => {
try {
const { ip } = await pGet(IP_URL)
const preference = await pGet(`${PREFERENCE_URL_PREFIX}${ip}`)
//do something with the preference
} catch(err) {
// handle errors
}
}
go()
<script src="https://unpkg.com/#babel/standalone#7/babel.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
If you don't have to use jQuery, I'd avoid it and use the fetch API provided by modern host environments.
const IP_URL = 'https://ipinfo.io/json'
const PREFERENCE_URL_PREFIX = 'https://www.example.com/getDomain.php?ip='
const go = async () => {
try {
const { ip } = await (await fetch(IP_URL)).json()
const preference = await fetch(`${PREFERENCE_URL_PREFIX}${ip}`)
//do something with the preference
} catch(err) {
// handle errors
}
}
go()
<script src="https://unpkg.com/#babel/standalone#7/babel.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

You need to return a Promise from check_saved_url. Inside of the Promise, you then need to use resolve to replace return. You can also use reject(new Error("error")) if there was an error.
async function check_saved_url() {
return new Promise((resolve, reject) => {
$.get("https://ipinfo.io/json", function (response) {
current_ip = response.ip;
location_here = response.country;
var ajax_get_url_prefix = base_url + 'getDomain.php?ip=';
var key_ip = current_ip + '###' + domain_permanent;
var url = ajax_get_url_prefix + key_ip;
$.get(url, function(data, status) {
console.log("Data: " + data + "\nStatus: " + status);
if (data.includes('0 results')) {
// Instead of return, use resolve()
//return 'unknown';
resolve('unknown');
// You can also pass an error with `reject(new Error("error"))`
} else {
// Instead of return, use resolve()
//return data;
resolve(data);
}
});
}, "jsonp");
});
}
const func1 = async() => {
return await check_saved_url();
}
const func2 = async() => {
let domain_preference = '';
domain_preference = await func1();
console.log("domain_preference: ",domain_preference);
}
func2();
You can read more about promises on MDN

There is no promise returned by check_saved_url nor is $.get returning a promise. You need to wrap it all in a promise
async function check_saved_url() {
return new Promise((resolve, reject) => {
$.get("https://ipinfo.io/json", function (response) {
current_ip = response.ip;
location_here = response.country;
var ajax_get_url_prefix = base_url + 'getDomain.php?ip=';
var key_ip = current_ip + '###' + domain_permanent;
var url = ajax_get_url_prefix + key_ip;
$.get(url, function(data, status) {
console.log("Data: " + data + "\nStatus: " + status);
if (data.includes('0 results')) {
resolve('unknown');
} else {
resolve(data);
}
})
.fail(reject);
}, "jsonp")
.fail(reject);
})
}
Clean the code
To have a cleaner and reusable code you can wrap $.get in a Promise
async function async_get(url) {
return new Promise((resolve, reject) => {
const jqxhr = $.get(url);
jqxhr.done(() => resolve({ json: jqxhr.responseJSON, status: jqxhr.status }));
jqxhr.fail(() => reject(jqxhr));
})
}
async function check_saved_url() {
const { json: response } = await async_get("https://ipinfo.io/json");
current_ip = response.ip;
location_here = response.country;
var ajax_get_url_prefix = base_url + 'getDomain.php?ip=';
var key_ip = current_ip + '###' + domain_permanent;
var url = ajax_get_url_prefix + key_ip;
const { json: data, status } = await $.get(url);
console.log("Data: " + data + "\nStatus: " + status);
return data.includes('0 results') ? 'unknown' : data;
}

Your function check_saved_url does not return a promise - this is why await is not working for it.
The solution would be to return a promise in check_saved_url.
Suppose I have a function create_promise which I want to await (this mimics your 'get' request):
function create_promise() {
return new Promise(resolve => {
setTimeout(() => resolve("Done!"), 500);
});
}
const func1 = async() => {
const result = await create_promise();
return result + " <- result";
};
func1().then(console.log);

I will suggest you to use fetch API check_saved_url(). It return promise. You can read more about it here. https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
Code will go like this :
async function check_saved_url() {
let connect = await fetch("https://ipinfo.io/json")
let response = await connect.json()
current_ip = response.ip;
location_here = response.country;
var ajax_get_url_prefix = base_url + 'getDomain.php?ip=';
var key_ip = current_ip + '###' + domain_permanent;
var url = ajax_get_url_prefix + key_ip;
// $.get(url, function(data, status) {
// console.log("Data: " + data + "\nStatus: " + status);
// if (data.includes('0 results')) {
// return 'unknown';
// } else {
// return data;
// }
// });
// }, "jsonp");
}

Related

Javascript fetch exception causes dead stop

I have the following code:
function makeid(length) {
var result = '';
var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
var charactersLength = characters.length;
for ( var i = 0; i < length; i++ ) {
result += characters.charAt(Math.floor(Math.random() *
charactersLength));
};
return result;
};
var instance = "{{ user }}" + makeid(16);
var checksum = "First Request Not recieved";
console.log(instance);
function downloadPlay(){
console.log("\ndownloadPlay - Begin\n")
try{
fetch("/file?instance=" + instance + "&checksum=" + checksum)
.then(function(resp) {
resp.headers.forEach(
function(val, key) {
// console.log("key, val: " + key + ", " + val);
if(key == "checksum"){
console.log("checksum: " + val);
checksum = val;
};
}
);
}
)
.then(file => {
var audio = new Audio("/file?instance=" + instance + "&checksum=" + checksum);
console.log("Done");
audio.addEventListener('ended', (event) => {
delete audio;
downloadPlay();
});
audio.play();
}
)
} catch (error) {
console.log("Something went wrong, Retrying: " + error);
}
console.log("downloadPlay - Complete\n")
};
downloadPlay();
This works perfectly when the promise succeeds. However when it fails(such as when the client device switches networks, i.e. wifi to data or just different access points on the same wifi network) it stops dead and never resumes no matter how many while loops, extra recursion points or try and catch statements I use. The best I could do so far is get it to play ever increasing numbers of the audio mostly in sync with each other and I just dont understand why. It seems I have a general lack of understanding of how this promise thing actually functions, but no matter how many tutorials I read/watch my lack of understanding seems to remain unchanged.
Heres the code that somewhat worked if that helps:
function makeid(length) {
var result = '';
var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
var charactersLength = characters.length;
for ( var i = 0; i < length; i++ ) {
result += characters.charAt(Math.floor(Math.random() *
charactersLength));
};
return result;
};
var instance = "{{ user }}" + makeid(16);
var checksum = "First Request Not recieved";
console.log(instance);
function downloadPlay(){
console.log("\ndownloadPlay - Begin\n")
try{
console.log('fetching')
fetch("/file?instance=" + instance + "&checksum=" + checksum)
.then(function(resp) {
resp.headers.forEach(
function(val, key) {
// console.log("key, val: " + key + ", " + val);
if(key == "checksum"){
console.log("checksum: " + val);
checksum = val;
};
}
);
}
).catch(function(error) {
console.log('request failed', error)
console.log('retrying')
downloadPlay();
return;
})
.then(file => {
var audio = new Audio("/file?instance=" + instance + "&checksum=" + checksum);
console.log("Done");
audio.addEventListener('ended', (event) => {
delete audio;
downloadPlay();
});
audio.play();
}
)
} catch (error) {
console.log("Something went wrong, Retrying: " + error);
}
console.log("downloadPlay - Complete\n")
};
downloadPlay();
Any solution or very simple explanation on what im doing wrong would be much appreciated
Thanks in advance :)
You can do something like this
Just remove the comment and use your original fetching function
You can't use try catch with promises unless you use async await
const fakeChecking = Promise.resolve({headers: {checksum: 'aaaa'}})
const errorChecking = Promise.reject('error')
function downloadPlay(fetching) {
console.log("\ndownloadPlay - Begin\n")
console.log('fetching')
fetching
.then((resp) => resp.headers.checksum)
.then(checksum => {
/*var audio = new Audio("/file?instance=" + instance + "&checksum=" + checksum);
console.log("Done");
/*audio.addEventListener('ended', (event) => {
delete audio;
downloadPlay();
console.log("downloadPlay - Complete\n")
});
audio.play();*/
console.log("downloadPlay - Complete\n")
})
.catch(function(error) {
console.log('request failed', error)
console.log('retrying')
downloadPlay(fakeChecking);
})
};
downloadPlay(errorChecking);

Capture MySQL query result as var [js]

I'm currently trying to build a discord bot, and I want to use a database for some aspects of it. Currently, I'm trying to add a command that would return the names of all the tables I have in the database, and for the most part I have it down.
The part that I'm struggling with is actually getting the names back out as a var. Every guide and stackoverflow question that I've been able to find on it assume that you just want to get that result and then print it to the console, but I need to return it back to the method that called this.
My previous attempt was setting an outside variable and using a promise to wait for it to change, but I couldn't get that to work, most likely because I don't fully understand how Promises work. My current attempt uses setTimeout() to check, but that just returns the asyncID of either the first or second iteration.
Any help either in making either of these work or completely scrapping them and doing this a different way is very welcome.
Previous code:
function listTables() {
db.query('SELECT table_name FROM information_schema.tables WHERE table_schema=\'' + dbName + '\'', (error, results) => {
if(error) throw error;
let temp = '';
results.forEach((item) => {
temp += item.table_name + ', ';
}); temp = temp.slice(0, -2);
setReturn(temp);
});
let out = checkReturn().then((value) => {
return value();
}).catch((error) => {
console.log(error);
return '';
});
returnValue = null;
return out;
}
var returnValue = null;
function setReturn(value) {
returnValue = value;
}
async function checkReturn() {
console.log('Checking Return: ' + returnValue);
let promise = new Promise((resolve, reject) => {
if(returnValue === null) reject('Var not set');
else resolve(returnValue)
});
return await promise;
}
Current Code:
function listTables() {
setReturn(null);
db.query('SELECT table_name FROM information_schema.tables WHERE table_schema=\'' + dbName + '\'', (error, results) => {
if(error) throw error;
let temp = '';
results.forEach((item) => {
temp += item.table_name + ', ';
}); temp = temp.slice(0, -2);
setReturn(temp);
});
return checkReturn();
}
var returnValue = null;
function setReturn(value) {
returnValue = value;
}
function checkReturn() {
console.log('Checking Return: ' + returnValue);
if(returnValue === null) {
return setTimeout(checkReturn, 50);
} else {
return returnValue;
}
}
You need to modify the listTables function to return a promise.
function listTables() {
return new Promise((resolve, reject) => {
db.query('SELECT table_name FROM information_schema.tables WHERE table_schema=\'' + dbName + '\'', (error, results) => {
if(error) {
reject(error);
return;
}
let temp = '';
results.forEach((item) => {
temp += item.table_name + ', ';
}); temp = temp.slice(0, -2);
resolve(temp);
});
});
}
// Usage of `listTables()`
listTables()
.then(result -> {
// Do process result
});

Node js async convert to sync

I need to push data to array synchronously. First API request get image key base one that need to get image data within loop.
var deasync = require('deasync');
router.get('/a', function(req, res) {
var username="user";
var passw ="pass";
var op = [];
var args = {
headers: {
'Authorization': 'Basic ' + new Buffer(username + ':' + passw).toString('base64')
}
};
//this is first api request
client.get(global.apiUrl+"V1/ProductItem", args,
function (data, response) {
//this is second api request
data.forEach(function(img) {client.get(global.apiUrl+"V1/ImagePreview/"+img.AvatarKey,args,
function (data2, response){
img['data']=data2.Image;
deasync(pushData(img));
});
});
});
function pushData(img){
op.push(img);//array push
}
res.render('test1', { "out":JSON.stringify(op) });
});
As much as I think deasync is a poor choice of solving your particular issue, the key to using it, is to "deasync" asynchronous functions. As Array.push is synchronous, deasync'ing Array.push makes no sense
having read the documentation for deasync, it's fairly simple to use
var deasync = require('deasync');
// create a sync client.get
function syncClientGet(client, url, args) {
var inflight = true;
var ret;
client.get(url, args, function(data, response) {
// as your original code ignores response, ignore it here as well
ret = data;
inflight = false;
});
deasync.loopWhile(() => inflight);
return ret;
}
router.get('/a', function(req, res) {
var username = "user";
var passw = "pass";
var op = [];
var args = {
headers: {
'Authorization': 'Basic ' + new Buffer(username + ':' + passw).toString('base64')
}
};
let data = syncClientGet(client, global.apiUrl + "V1/ProductItem", args);
data.forEach(function(img) {
let data2 = syncClientGet(client, global.apiUrl + "V1/ImagePreview/" + img.AvatarKey, args);
img['data'] = data2.Image;
op.push(img);
});
res.render('test1', {
"out": JSON.stringify(op)
});
});
However, embracing asynchronicity, the code you posted could be easily written as
router.get('/a', function (req, res) {
var username = "user";
var passw = "pass";
var op = [];
var args = {
headers: {
'Authorization': 'Basic ' + new Buffer(username + ':' + passw).toString('base64')
}
};
client.get(global.apiUrl + "V1/ProductItem", args, function (data, response) {
data.forEach(function (img) {
client.get(global.apiUrl + "V1/ImagePreview/" + img.AvatarKey, args, function (data2, response) {
img['data'] = data2.Image;
op.push(img);
if (img.length == data.length) {
res.render('test1', {
"out": JSON.stringify(op)
});
}
});
});
});
});
or, using Promises
router.get('/a', function (req, res) {
var username = "user";
var passw = "pass";
var args = {
headers: {
'Authorization': 'Basic ' + new Buffer(username + ':' + passw).toString('base64')
}
};
// create a Promisified client get
var clientGetPromise = function clientGetPromise(client, url, args) {
return new Promise(function (resolve, reject) {
return client.get(url, args, function (data, response) {
return resolve(data);
});
});
};
clientGetPromise(client, global.apiUrl + "V1/ProductItem", args).then(function (data) {
return Promise.all(data.map(function (img) {
return clientGetPromise(client, global.apiUrl + "V1/ImagePreview/" + img.AvatarKey, args).then(function (data2) {
img['data'] = data2.Image;
return img;
});
}));
}).then(function (op) { // op is an Array of img because that's how Promise.all rolls
return res.render('test1', { "out": JSON.stringify(op) });
});
});

Before resolving the promise on service method,it goes to the main method's promise

I have written Angular Service as shown below.
getPropertyDetailsByUsingApiService.js
(function () {
appModule.service('getPropertyDetailsByUsingApiService', ['$http', function ($http) {
this.propertyDetails = function (token, number, street, county, zip) {
var endpointUrl = 'http://my.com/api/Matcher?Token=';
var url = endpointUrl + token + '&Number=' + number + '&Street=' + street + '&County=' + county + '&Zip=' + zip;
return $http.get(url).then(function (data) {
var result = data;
if (result.data[0].Status == 'OK') {
$http.get(endpointUrl + token + '&Krp=' + result.data[0].Result[0].KRP + '&County=' + county)
.then(function (finalData) {
return finalData;
});
}
});
};
}
]);
})();
This is the consuming method :
propertyForm.js
//to call Api
vm.callApi = function () {
var county = _.find(vm.counties, function (c) { return c.id == vm.property.countyId; });
var city = _.find(vm.cities, function (c) { return c.id == vm.property.address.cityId; });
getPropertyDetailsByUsingApiService.propertyDetails(vm.getMd5Hashbytes(), vm.property.address.streetNumber, vm.property.address.streetName,
county.name, city.zipCode).then(function (result) {
vm.propertyDetails = result;
});
};
Q : Issue here is before resolving the promise on service method,it goes to the main method's promise.In other words before resolving 2nd promise on the service method where it goes to the calling method's promise.Can you tell me where is the issue ?
You can rewrite:
return $http.get('url').then(function(r) => { return r;})
as:
var defer = $q.defer();
$http.get('url').then(function(r) => { defer.resolve(r);})
return defer.promise;
It does not make sense in usual case, but in special cases, you can do whatever with this construction:
var defer = $q.defer();
if (smth) {
defer.resolve('test1');
} else {
$http.get('url').then(function(r) => {
if (smth2) {
defer.resolve(r);
} else {
$http.get(..., function(r) => {
defer.resolve(r);
})
}
})
|
return defer.promise;
So, in the first promise on the service, you arent returning the second promise, so the first promise just finishes up and resolves as the method completes, and doesnt wait for the second promise.
return $http.get(url).then(function (data) {
var result = data;
if (result.data[0].Status == 'OK') {
//Add a return here
return $http.get(endpointUrl + token + '&Krp=' + result.data[0].Result[0].KRP + '&County=' + county)
.then(function (finalData) {
return finalData;
});
}
});

How to improve this code in Node.js and Express.js avoiding callback hell

I have a method in one of my controller. The purpose of the controller, is print an array of urls using webshot package.
This is the code in question:
router.post('/capture', function (req, res, next) {
//Check params remove
var json = JSON.parse(req.body.data);
var promise = new Promise(function (resolve, reject) {
var totalImages = Object.keys(json).length;
var arrayListUrlImages = new Array(totalImages);
var counter = 0;
var completeDir = dir + ''; //Directory URL
for (var value of json) {
var url = 'http://example.com/' + id + '/' + value.anotherValue;
var folder = completeDir + id + '/' + value.anotherValue + '.jpg';
//Options for capturing image
var options = {
renderDelay: 1000,
quality: 100,
phantomConfig:
{
'local-to-remote-url-access': 'true',
'ignore-ssl-errors': 'true'
}
};
var anotherValue = value.anotherValue;
(function (anotherValue) {
webshot(url, folder, options, function (err) {
// screenshot now saved
if (err === null) {
var urlImage = "http://example.com/images/" + id + "/" + anotherValue + ".jpg";
arrayListUrlImages.push(urlImage);
counter++;
console.log("Counter: " + counter);
if (counter === totalImages) {
resolve(arrayListUrlImages);
}
}
else {
reject(err);
}
});
})(anotherValue);
}
}).then(function (arrayImages) {
res.send(arrayImages);
}).catch(function (errorVale) {
res.send(null);
});
});
This code is working without problems... but I would like to do better. I don't know how many URLs need to check (this is important detail because I need to do a for each or similar).
I have read about async package... Is better option move this code to something like async.parallel? Can I use yield in my code?
Thanks!
Since you are using Promise, I recommend Promise.all.
It returns a promise that resolves when all of the promises in the iterable argument have resolved, or rejects with the reason of the first passed promise that rejects.
Seems like it solves your problem.
Example:
downloadOne = url => new Promise(resolve => {
webshot(url, ....., (err, res) => resolve(res));
})
router.post('/capture', function (req, res, next) {
var urls = JSON.parse(req.body.data);
Promise.all(urls.map(downloadOne)).then(req.send);
}
This is an example of code flow based on inner functions:
router.post('/capture', function (req, res, next) {
// Definitions
// Load image
function loadImage(value) {
var url = 'http://example.com/' + id + '/' + value.anotherValue;
var folder = completeDir + id + '/' + value.anotherValue + '.jpg';
//Options for capturing image
var options = {
renderDelay: 1000,
quality: 100,
phantomConfig:
{
'local-to-remote-url-access': 'true',
'ignore-ssl-errors': 'true'
}
};
return webshotPromise(url, folder, options);
}
// Load whebshot as a promise
function webshotPromise(url, folder, options) {
return new Promise((resolve, reject) => {
webshot(url, folder, options, function (err) {
if (err) {
reject(err);
}
var urlImage = "http://example.com/images/" + id + "/" + anotherValue + ".jpg";
resolve(urlImage);
}
});
}
// The method flow
const json = JSON.parse(req.body.data);
// Get json keys and iterate over it to load
Promise.all(
Object.getOwnPropertyNames(json).map(key => loadImage(json[key]))
)
// Got list of urls
.then((list) => {
res.json(list);
}, (error) => {
console.error(error);
res.json(null);
});
});
You don't need to use async for such simple example. Use native promises:
router.post('/capture', function (req, res, next) {
//Check params remove
const json = JSON.parse(req.body.data);
Promise.all(Object.getOwnPropertyNames(json).map((key) => {
var value = json[key];
var url = 'http://example.com/' + id + '/' + value.anotherValue;
var folder = completeDir + id + '/' + value.anotherValue + '.jpg';
//Options for capturing image
var options = {
renderDelay: 1000,
quality: 100,
phantomConfig:
{
'local-to-remote-url-access': 'true',
'ignore-ssl-errors': 'true'
}
};
return new Promise((resolve, reject) => {
webshot(url, folder, options, function (err) {
if (err) {
reject(err);
return;
}
var urlImage = "http://example.com/images/" + id + "/" + anotherValue + ".jpg";
resolve(urlImage);
}
});
}))
.then((listOfUrls) => {
res.json(listOfUrls); // List of URLs
}, (error) => {
console.error(error);
res.json(null);
});
});
Honestly, your code looks fine.
If you are not going to add more logic here, leave it as it is.
What can be done better, is migrating it to ES6 syntax and extracting anotherValue function but I am not aware if this applicable for your case.

Categories