Async/Await Not Waiting as I expect it - javascript

Please bear with me I've been dropped into a new project and trying to take it all in. I've been making progress over the last couple of day but can't seem to get over this last hump. Hopefully I can explain it correctly.
I'm loading up a web form and need to make a call out to the API to get some information that may or may not be present based on the data currently loading up. I've simplified my page to basically be this.
...Get some information the user wants and start to do some work to load
up the page and set up the form.
...old stuff working fine...
//Time for my new stuff
var testValue
async function test() {
await http.post(appConfig.serviceRootUrl + '/api/XXX/YYY',
{ mProperty: myObject.collectionInObject.itemInCollection }).then(function (result) {
if (result.length < 1) {
testValue= false;
}
else if (result[0].infoIWant.trim().length > 0) {
testValue= true;
}
});
}
test();
//Originally above in the if I was just seeing if I got a result
//and setting testValue to true/false but changed it for debugging
//and I am surely getting back correct values when the data exists
//or result.length zero when no data for it
...Do a bunch of more stuff that is old and working correctly....
//Test the new stuff up above
alert(testValue);
Most of the time I get back the correct true or false in the alert but once in a while I get back undefined. I'm guessing the undefined is because it is getting to the alert before the async/await finishes. I was under the impression it won't go past the line where I call "test();". I thought it was in effect making it halt anything below test(); until the await finished. Originally it was a bit more complex but I keep stripping it down to make it (hopefully) more basic/simple.
What am I missing in my thoughts or implementation?
Any help greatly appreciated as I'm chasing my tail at this point.

This isn't how async functions work. The function only appears to wait inside the function itself. Outside the function it is called and returns a promise synchronously.
In other words if you write:
let t = test()
t will be a promise that resolves when test() returns. In your current code, if you want to respond outside the function you would need something like:
async function test() {
let result = await http.post(appConfig.serviceRootUrl + '/api/XXX/YYY',
{ mProperty: myObject.collectionInObject.itemInCollection })
if (result.length < 1) return false
else if (result[0].infoIWant.trim().length > 0) return true;
}
// test is an async function. It returns a promise.
test().then(result => alert("result: " + result ))
Edit based on comments
Here's a working version using Axios for the http.post command:
async function test() {
let result = await axios.post('https://jsonplaceholder.typicode.com/posts',
{ mProperty: "some val" })
return result.data
}
// test is an async function. It returns a promise.
test().then(result => console.log(result ))
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js"></script>

How about doing this?
...Get some information the user wants and start to do some work to load
up the page and set up the form.
...old stuff working fine...
//Time for my new stuff
var testValue
async function test() {
let promise = new Promise( (resolve, reject) => resolve( http.post(appConfig.serviceRootUrl + '/api/XXX/YYY',
{ mProperty: myObject.collectionInObject.itemInCollection }).then(function (result) {
if (result.length < 1) {
testValue= false;
}
else if (result[0].infoIWant.trim().length > 0) {
testValue= true;
}
})));
await promise;
alert(testValue);
}
test();
//Originally above in the if I was just seeing if I got a result
//and setting testValue to true/false but changed it for debugging
//and I am surely getting back correct values when the data exists
//or result.length zero when no data for it
...Do a bunch of more stuff that is old and working correctly....
//Test the new stuff up above

If you use .then() syntax, don't await, and vice versa. I was incorrect about browser compatibility with async/await, seems I haven't kept up with browser scripting, in favor of Node. But also, since you're using jQuery, $.ajax() might be a good option for you, because you don't need async/await or .then(), and you can do like so:
$.ajax(appConfig.serviceRootUrl + '/api/XXX/YYY', {
method: 'POST',
data: { mProperty: myObject.collectionInObject.itemInCollection }
}).done(function(data) {
//use result here just as you would normally within the `.then()`
})
I hope this is more helpful than my original answer.

Related

jQuery Promise async await in an object

I have a object with loads of functions inside. some of them are ajax calls. the idea is to call one of this functions from another place and wait until it's resolved but i don't know if it is possible and if it is im not sure how to do it. here is the code so you get an idea of what i'm trying to do.
$(document).ready(function(){
$(".button").click(function(){
let some_id = $(this).val();
let ajax_test = binder.firstLayer['ajaxcall'](some_id);
console.log(ajax_test);
});
});
Then i have an object with all the functions i use, among them this ajax call looking something like this:
let binder = {
firstLayer : {
ajaxcall: function(some_id){
return new Promise( (resolve, reject) => {
$.ajax({
// all the details
}).then((response) => {
resolve(response);
});
});
}
}
}
I haven't included the keywords async and await because im not sure where to put them. in the case of the ajaxcall function, i tried putting the async after the ajaxcall: and before function(some_id) but when i put await like:
let ajax_test = await binder.firstLayer['ajaxcall'](some_id);
but it doesn't work. so again, the question is if it can be done the way im trying to do it and if so, what im doing wrong or putting in the wrong place. with this attempt i get undefined if i try to look into the response or [object Promise] (from console.log(ajax_test);) if i just console log the response (from same place as before)
you can use let ajax_test = await binder.firstLayer['ajaxcall'](some_id); as long as you mark the function as async:
$(".button").click(async function(){ ...

How to wait for this function to finish?

I need to wait for mapping function to finish before I send the data to the console. I know it has something to do with Promise. I've been trying for hours and I couldn't get it to work even after reading so much about promises and async functions...
async function inactiveMemberWarner() {
var msg = "```javascript\nI have sent warnings to members that have been inactive for 2 weeks.\n\n"
var inactiveMembers = '';
var count = 0;
var guildMembers = client.guilds.find(g => g.name === mainGuild).members;
const keyPromises = await guildMembers.map(async (member) => {
if (isMod(member)) {
connection.query(`SELECT * from users WHERE userID='${member.id}'`, (err, data) => {
if (data[0]) {
if (!data[0].warnedForInactivity && moment().isSameOrAfter(moment(data[0].lastMSGDate).add('2', 'week'))) {
count++;
var updateWarning = {warnedForInactivity: 1}
connection.query(`UPDATE users SET ? WHERE userID='${data[0].userID}'`, updateWarning);
member.send(`**[*]** WARNING: You've been inactive on \`\`${mainGuild}\`\` for 2 weeks. Members that have been inactive for at least a month will be kicked.`);
inactiveMembers += `${count}. ${member.user.tag}\n`;
return inactiveMembers;
}
}
});
}
});
await Promise.all(keyPromises).then(inactiveMembersData => console.log(inactiveMembers)); // RETURNS AN EMPTY STRING
setTimeout(() => console.log(inactiveMembers), 5000); // RETURNS THE INACTIVE MEMBERS AFTER WAITING FOR 5 SECONDS (PRMITIVE WAY)
}
inactiveMemberWarner();
Thank you in advance!
You're close, but not quite there.
First, some notes:
await can be used on any value, but it is entirely pointless to use it on anything that isn't a Promise. Your guildMembers.map(...); returns an array, not a Promise.
Mixing await and .then(...) works, but is kinda messy. You're already using await - why bother dealing with callbacks?
Using guildMembers.map(async ...) like this will ensure that all the requests are fired more or less instantaneously, and they could finish in any order. This is fine, but it is kind of a race condition and results in a more or less random order of results.
This is not a good approach even just conceptually! Any time you ever have to loop queries, try and investigate ways to do it in only one query. SQL is quite powerful.
The reason your current code doesn't work is because your connection.query function escapes the async control flow. What I mean by this is that the whole point of using async/await and Promises is basically to keep track of the callbacks locally, and to make use of promise chaining to dynamically add callbacks. If you call an async function which returns a Promise, you can now carry that Promise anywhere else in your code and attach a success handler to it dynamically: either with .then() or with the sugar await.
But the connection.query function doesn't return a Promise, it just has you pass another naked callback - this one is not being tracked by a Promise! The Promise doesn't have a reference to that callback, it can't know when that callback is getting called, and thus your async/await control flow is escaped and your promises resolve long before the queries have ran.
You can resolve this by making a new Promise in the async function:
async function inactiveMemberWarner() {
var msg = "```javascript\nI have sent warnings to members that have been inactive for 2 weeks.\n\n"
var inactiveMembers = '';
var count = 0;
var guildMembers = client.guilds.find(g => g.name === mainGuild).members;
const keyPromises = guildMembers.map(async (member) => {
if (isMod(member)) {
return new Promise((resolve, reject) => {
connection.query(`SELECT * from users WHERE userID='${member.id}'`, (err, data) => {
if (err) reject(err); //make errors bubble up so they can be handled
if (data[0]) {
if (!data[0].warnedForInactivity && moment().isSameOrAfter(moment(data[0].lastMSGDate).add('2', 'week'))) {
count++;
var updateWarning = {warnedForInactivity: 1}
connection.query(`UPDATE users SET ? WHERE userID='${data[0].userID}'`, updateWarning);
member.send(`**[*]** WARNING: You've been inactive on \`\`${mainGuild}\`\` for 2 weeks. Members that have been inactive for at least a month will be kicked.`);
resolve(`${count}. ${member.user.tag}\n`;);
}
} else resolve(""); //make sure to always resolve or the promise may hang
});
});
}
});
let inactiveMembersData = await Promise.all(keyPromises); // Returns an array of inactive member snippets.
inactiveMembers = inactiveMembersData.join(""); //join array of snippets into one string
}
inactiveMemberWarner();
This will work, but there is a much much much better way. SQL supports the IN operator, which allows you to have conditions like WHERE userID IN (list_of_ids). In other words, you can do this in one query. You can even specify more conditions, such as warnedForInactivity = 0 and lastMSGDate BETWEEN (NOW() - INTERVAL 14 DAY) AND NOW(). This way you can offload all of your current processing logic onto the SQL server - something that you should try to do virtually every single time you can. It would simplify this code a lot too. I won't go any further as it's out of scope for this question but feel free to ask another if you can't figure it out.
I can't test this, but this is what normally works for me is when wanting to wait on smt:
async function inactiveMemberWarner() {
new Promise(function(cb,rj){
var msg = "```javascript\nI have sent warnings to members that have been inactive for 2 weeks.\n\n"
var inactiveMembers = '';
var count = 0;
var guildMembers = client.guilds.find(g => g.name === mainGuild).members;
const keyPromises = await guildMembers.map(async (member) => {
if (isMod(member)) {
connection.query(`SELECT * from users WHERE userID='${member.id}'`, (err, data) => {
if (data[0]) {
if (!data[0].warnedForInactivity && moment().isSameOrAfter(moment(data[0].lastMSGDate).add('2', 'week'))) {
count++;
var updateWarning = {warnedForInactivity: 1}
connection.query(`UPDATE users SET ? WHERE userID='${data[0].userID}'`, updateWarning);
member.send(`**[*]** WARNING: You've been inactive on \`\`${mainGuild}\`\` for 2 weeks. Members that have been inactive for at least a month will be kicked.`);
inactiveMembers += `${count}. ${member.user.tag}\n`;
cb(inactiveMembers);
}
}
});
}
});
cb('No Members');
}).then(inactiveMembersData => console.log(inactiveMembers)); // SHOULD RETURNS THE INACTIVE MEMBERS
}
inactiveMemberWarner();

Wait until async is completed (await)

I am trying to get my head around with async/await
I am using https://github.com/sunnylqm/react-native-storage for my project in react-native.
I am using async storage to store few critical information such as selected user locale, I need to retrieve this value before screen is rendered to display screen based on selected locale.
I have tried implementing several helper functions, it works with callback, what I need is to return the value instead of callback and wait until the value is fetched. Below are few examples I tried.
// 1
_selectedLocale = async () => {
try {
const value = await global.storage.load({key: 'selectedLocale'});
return value
} catch (error) {
console.log(value)
}
}
var selectedLocale = _selectedLocale();
// 2
export async function _selectedLocale() {
return storage.load({key: 'selectedLocale'});
}
var selectedLocale = _selectedLocale();
// 3
export function selectedLocale(callback) {
storage.load({key: 'selectedLocale'}).catch(e => {
callback(RNLanguages.language);
}).then(function(locale) {
callback(locale);
});
}
This is not working, I am looking to
Implement a helper function to retrieve a value based on key
Wait until the value is retrieved (sync)
Can someone point me to right direction
Thanks
UPDATE1:
Here is how I am using the callback
selectedLocale(function(locale) {
global.layoutIsRTL = 'ar' == locale;
ReactNative.I18nManager.allowRTL(true);
global.i18n.locale = locale
});
UPDATE2:
It seems someone has already done this, here is the reference https://github.com/sunnylqm/react-native-storage/issues/206 unfortunately I am having hard time understanding it.
async implicitly returns a promise, so you need to await the function call as well if you want the value there:
var selectedLocale = await _selectedLocale();
but you can only do that inside of another async function

How to GET from API synchronously in Node.js?

TLDR: How to use ES6 fetch to download synchronously?
I'm trying to write a script in Node to download data from an API till there is no more to download e.g. the endpoint has a dataset of size roughly 12000 but only provides 100 per call, and I need all the data. So I've decided to have it downloaded synchronously, and only stop when the json returned is finally empty.
// function to make one api GET call
getData = (offset) => {
return fetch('http://...'+'?start=' + offset)
.then(...)
}
// make api calls until json is finally empty,
// indicating that I've downloaded all the data
offset = 0
const results = getData(offset)
while (results.length != 0) {
// combine results...
i += 100 // move offset
results = getData(i)
}
Because I don't know precisely how large the data is and at which offset it ends, whether or not to make another call depends on the last one.
The code above fails because the promise from getData() does not resolve in time for the while loop. In another language, this would be okay as getData blocks until it is completed. I've tried to await getData but it needs to be in an async (which I don't know where to place, I already have promises). Is there any way to force getData() to block until it is resolved?
you can mark your getData() function as async
async getData() {
return fetch('http://...'+'?start=' + offset)
.then(...)
}
then await for the returned promise completion
await const results = getData(offset)
Or you can modify your code to handle the logic of whether to make another call in the promise callbacks. Something like
function fetchData(offset) {
if (offset < 1000) {
return Promise.resolve([1,2,3].map(i=>offset+i));
} else {
return Promise.resolve([]);
}
}
function needsMoreData(results) {
return results.length > 0;
}
function fetchNextDataIfNeeded(results, offset) {
if (needsMoreData(results)) {
return fetchRemainingData(offset + 100)
.then(nextResults=>[...results, ...nextResults]);
}
return Promise.resolve(results);
}
function fetchRemainingData( offset) {
return fetchData(offset)
.then(results=>fetchNextDataIfNeeded(results, offset));
}
function fetchAllData() {
return fetchRemainingData(0)
.then(results=>console.log(results));
}
fetchAllData();
See https://jsfiddle.net/rwycbn5q/1/
I recently found myself in a similar situation, but some functions cannot be made synchronously. But you could do something like this:
let results=[];
getData(idx)=>{
fetch("url").then(data=>{
if(data!=={}){ //Or some other continuation check
results.push(data); //Or whatever you want to do with the data
getData(idx+100);
}
})
}
getData(0);

Call hierarchy of async functions inside a loop?

There's a async call I'm making that queries a database on a service, but this service has a limit of how many it can output at once, so I need to check if it hit its limit through the result it sends and repeat the query until it doesn't.
Synchronous mockup :
var query_results = [];
var limit_hit = true; #While this is true means that the query hit the record limit
var start_from = 0; #Pagination parameter
while (limit_hit) {
Server.Query(params={start_from : start_from}, callback=function(result){
limit_hit = result.limit_hit;
start_from = result.results.length;
query_result.push(result.results);
}
}
Obviously the above does not work, I've seen some other questions here about the issue, but they don't mention what to do when you need each iteration to wait for the last one to finish and you don't know before hand the number of iterations.
How can I turn the above asynchronous? I'm open to answers using promise/deferred-like logic, but preferably something clean.
I can probably think of a monstruous and horrible way of doing this using waits/timeouts, but there has to be a clean, clever and modern way to solve it.
Another way is to make a "pre-query" to know the number of features before hand so you know the number of loops, I'm not sure if this is the correct way.
Here we use Dojo sometimes, but the examples I found does not explain what to do when you have an unknown amount of loops https://www.sitepen.com/blog/2015/06/10/dojo-faq-how-can-i-sequence-asynchronous-operations/
although many answers already, still I believe async/await is the cleanest way.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
and you might need babel
https://babeljs.io/
JS async logic syntax changed from callback to promise then to async/await, they all do the same thing, when callback nests a lot we need something like a chain, then promise come, when promise goes in loop, we need something make the chain more plain more simple, then async/await come. But not all browsers support the new syntax, so babel come to compile new syntax to old syntax, then you can always code in new syntax.
getData().then((data) => {
//do something with final data
})
async function getData() {
var query_results = [];
var limit_hit = true;
var start_from = 0;
//when you use await, handle error with try/catch
try {
while (limit_hit) {
const result = await loadPage(start_from)
limit_hit = result.limit_hit;
start_from = result.results.length;
query_result.push(result.results);
}
} catch (e) {
//when loadPage rejects
console.log(e)
return null
}
return query_result
}
async function loadPage(start_from) {
//when you use promise, handle error with reject
return new Promise((resolve, reject) => Server.Query({
start_from
}, (result, err) => {
//error reject
if (err) {
reject(err)
return
}
resolve(result)
}))
}
If you want to use a loop then I think there is no (clean) way to do it without Promises.
A different approach would be the following:
var query_results = [];
var start_from = 0;
funciton myCallback(result) {
if(!result) {
//first call
Server.Query({ start_from: start_from}, myCallback);
} else {
//repeated call
start_from = result.results.length
query_result.push(result.results);
if(!result.limit_hit) {
//limit has not been hit yet
//repeat the query with new start value
Server.Query({ start_from: start_from}, myCallback);
} else {
//call some callback function here
}
}
}
myCallback(null);
You could call this recursive, but since the Query is asynchronous you shouldn't have problems with call stack limits etc.
Using promises in an ES6 environment you could make use of async/await. Im not sure if this is possible with dojo.
You don't understand callbacks until you have written a rate limiter or queue ;) The trick is to use a counter: Increment the counter before the async request, and decrement it when you get the response, then you will know how many requests are "in flight".
If the server is choked you want to put the item back in the queue.
There are many things you need to take into account:
What will happen to the queue if the process is killed ?
How long to wait before sending another request ?
Make sure the callback is not called many times !
How many times should you retry ?
How long to wait before giving up ?
Make sure there are no loose ends ! (callback is never called)
When all edge cases are taken into account you will have a rather long and not so elegant solution. But you can abstract it into one function! (that returns a Promise or whatever you fancy).
If you have a user interface you also want to show a loading bar and some statistics!
You must await for the server response every time. Here a encapsulated method
var query = (function(){
var results = [];
var count = 0;
return function check(fun){
Server.Query({ start_from: count}, function(d){
count = d.results.length;
results.push(d.results);
if (d.limit_hit && fun) fun(results);
else check(fun);
});
};
})();
// Call here
var my_query = query(function(d){
// --> retrive all data when limit_hit is true)
});
You can use a generator function Generators to achieve this
For POC:
some basics
- You define a generator with an asterick *
- it exposes a next function which returns the next value
- generators can pause with yield statement internally and can resume externally by calling the next()
- While (true) will ensure that the generator is not done until limit has reached
function *limitQueries() {
let limit_hit = false;
let start_from = 0;
const query_result = [];
while (true) {
if (limit_hit) {break;}
yield Server.Query(params={start_from : start_from},
callback=function* (result) {
limit_hit = result.limit_hit;
start_from = result.results.length;
yield query_result.push(result.results);
}
}
}
So apparently, the generator function maintains its own state. Generator function exposes two properties { value, done } and you can call it like this
const gen = limitQueries();
let results = [];
let next = gen.next();
while(next.done) {
next = gen.next();
}
results = next.value;
You might have to touch your Server.Query method to handle generator callback. Hope this helps! Cheers!

Categories

Resources