Wait until async is completed (await) - javascript

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

Related

Get promise value inside loop

I'm a beginner in Javascript/NodeJS and I'm prefer practical work, I'm practicing by creating a bot discord with discord.js
The objective of this one is simply to display me the current value of the bitcoin in USD.
To get the value of BTC, I use the coingecko-api package.
However I'm blocked because although I can get the value of bitcoin, I can't do it when I'm in a loop without really understanding why.
main.js :
var coin = require('./coingecko');
while(true) {
coin.btc().then((value => console.log (value) )); // <= This show nothing
// Other things
...
}
coingecko.js :
const CoinGecko = require('coingecko-api');
const CoinGeckoClient = new CoinGecko();
module.exports = {
btc: async function btc() {
let btc = await CoinGeckoClient.coins.fetch('bitcoin', {});
return btc['data']['market_data']['current_price']['usd'];
}
}
Any tips ? Thank's
Put your loop into an async function. Using await rather than .then() makes your looping logic much more straightforward.
const coin = require('./coingecko');
/* declare the async function */
async function doLoop () {
while(true) {
const value = await coin.btc()
console.log(value)
// Other things
...
}
}
/* invoke the async function from non-async context */
doLoop().catch(console.error)
Are you sure about the ["data"] part?
When I test this API here I get this response: https://www.coingecko.com/en/api/documentation
"market_data": {
"current_price": {
"aed": 183098,
"ars": 5097411,
...
"uah": 1356629,
"usd": 49850,
Also you can probably just do btc.market_data.current_price.usd

Promise resolves earlier than expected and not returning an AXIOS call value

I'm making a request to a service that generates several strings in rapid succession. My problem arise from having to return a promise with this service, as I do not control the service process that returns the string.
I want to emphasize the fact that I necessarily need to return a promise.
Right now, what I have is the main function (handler.ts) and it doesn't mantain any business logic, including only the following:
public static callback: any;
public static async init(configuration, callback): Promise<any> {
this.callback = callback;
...
return new Promise((resolve, reject) => {
try {
const result = await Service.bootstrap(configuration)
return resolve(result)
} catch(err) {
reject(err)
}
}
}
This handler calls the service, which has the bootstrap function that performs a call to a .js file that obtains some information from my computer and then returns a string about it.
import loadComputerInformation from 'load.js'
public async bootstrap() : Promise<any> {
loadComputerInformation();
function useComputerInfoString() {
// window.getInfo is generated by loadComputerInformation(), and I can not control
// when does it return it, as it generates several strings with rapid succession
// and until the function exists, I will not get my string.
if (typeof window.getInfo !== 'function') {return;}
const data = window.getInfo();
if (data.finished) {
clearTimeout(timeoutId);
const infoString = data.computerInfo;
Service.axiosCall(infoString);
}
}
// I have to set an interval to perform the call several times, and until it resolves it
// it will not stop performing this call.
const timeoutId = setInterval(useComputerInfoString, 500);
}
return;
}
Therefore, the problem that I'm facing is that my promise gets lost in another thread, and I can not return the value from Service.axiosCall(infoString), which is just a standard axios call, but that necessarily needs the infoString.
Adding the axios call function just in case it is useful. Right now, I'm using the callback passed to the handler.js to return the axios call, but I want to be able to include it in the Promise without the necessity of a callback function
public static async axiosCall(blackbox): Promise<any> {
await axios.post('https://somecall.com/info', blackbox)
.then((response) => { this.callback(element)
return element);
}
}
Any idea of how to solve this?
Highlight
Please note that loadComputerInformation() asynchronously loads Window.getInfo(), but it does not resolve only one value, but several, therefore I can not await on Window.getInfo() because at the beggining it does not exist, and will only return undefined
Also, right now, the code is up and running, but the way it is returning the value is with the callback and not as a promise.
Try a bootstrap function whose returned promise resolves with the response returned from an axios call.
This suggestion (based on information in the post) is vanilla JavaScript to demonstrate how to the problem might be approached. Obviously modification to integrate it into the application will be needed:
const bootstrap = () => new Promise( resolve => {
loadComputerInformation();
let state = "load";
const timer = setInterval( ()=> {
if( state == "load") {
if( typeof window.getData != "function") {
return;
}
state = "get";
}
let data;
if( state == "get") {
data = window.getData();
if(!data.finished) {
return;
}
// state = "request";
}
clearInterval(timer);
resolve( axios('post', "https:example.com/info", data.computerInfo) );
}, 100); // 1/10th second?
};
Note this answer has been modified to use a state machine to wait for getData to be loaded asynchronously, then to wait for a call to getData to return a data object with a finished property, before resolving the promise returned with the promise returned by the axios call - the first answer was simpler right up until it needed to wait for getData code to be loaded asynchronously.
Beneath the question lies a problem that may have to be written off to code debt: JavaScript is event driven. loadComputerInformation appears to have been written for its results to be polled. It is hard to imagine a justifiable reason why it was left in that state.

how do I assign a returned value from an async function to a variable

I am new to JavaScript and have been trying to read up a lot on why this is not working. Here is my code. I have also read a number of articles here on stack overflow but still feeling dense
Also if my title does not make sense, please suggest an edit
listRef.listAll()
.then(response => {
let files = []
response.items.forEach(item => {
var text
getText(item.name).then(res=>{text = res});
const id = {uid: guid()}
const url = item.getDownloadURL().then(url => {return url} )
const gsurl = `gs://archivewebsite.appspot.com/${folder}/${item.name}`
files.push({...item, name:item.name, url, gsurl, id:id.uid, text})
});
this.files = files;
})
.catch(error => console.log(error));
async function getText(docID) {
var docRef = firestore.collection("recipes").doc(docID);
let doc = await docRef.get()
if (doc.exists){
return doc.data().text
}
}
that code "works" in that it logs the response to the console but the text variable is a pending promise object.
I understand that async functions return a promise so when I call getText I need to use .then - what I am struggling with and have refactored this code a few times is this:
how can I assign the value of doc.data().text to a variable to be used later in other words, how can var text be an actual string and not a promise object pending
Also for my own learning on javascript inside the async function if I replace
if (doc.exists){
return doc.data().text
}
with
if (doc.exists){
return Promise.resolve(doc.data().text)
}
I get the same result in console.log - is this expected? is return simply short hand for the handler to resolve the promise?
I have also refactored this code to be non async and I get the same result where my var text is basically a pending promise and never the resolved data
Thanks for your help - also any articles to help explain this to me would be great! I have been going through courses on udemy but little confused by this right now
Actually you are assigning the complete promise to the variable text
Replace
var text = getText(item.name).then(res=>console.log(res))
by
var text = await getText(item.name);
OR
var text
getText(item.name).then(res=>{text = res});
Of course text is going to be a Promise. Promise.then() always returns a Promise.
Consider this code:
function doA(n) {
// do something here...
console.log("A" + n);
}
asnyc function doB(n) {
// do something here...
console.log("B" + n);
}
doA(1);
doA(2);
doB(3); // async
doA(4);
doB(5); // async
doB(6); // async
doA(7);
What do you expect the output to be?
1, 2, 4, and 7 will always be in order, because they are executed synchronously.
3 will not ever print before 1 and 2. Likewise, 5 and 6 will not ever print before 1, 2, and 4.
However, 3, 5, and 6 can be printed in any order, because async functions do not guarantee execution order once created.
Also, 4 can print before 3. Likewise, 7 can print before 5 and 6.
Basically, think of async functions as a parallel task that runs independently (although not really; single thread JS only simulates this behavior). It can return (fulfill/reject) at any moment. For this reason, you cannot just simply assign a return value of an async function to a variable using synchronous code - the value is not guaranteed to be (and probably is not) available at the moment of synchronous execution.
Therefore you need to put all the code that requires the value of text to be set into the callback block of the Promise, something like this:
getText(item.name).then((text) => {
// put everything that uses text here
});
This can of course lead to the infamous "callback hell", where you have layers inside layers of async callback. See http://callbackhell.com for details and mitigation techniques.
async/await is just one of the newer ways to do the same thing: MDN has an excellent article here: https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Async_await
OK I worked with someone at work and found a solution - it was related to this post
https://stackoverflow.com/a/37576787/5991792
I was using async function inside a for each loop
the refactored code is here
async function buildFiles(){
let items = await listRef.listAll()
let files = []
for (const item of item.items){
const text = await getText(item.name)
const url = await item.getDownloadURL()
const gsurl = `gs://archivewebsite.appspot.com/${folder}/${sermon.name}`
files.push({...item, name:item.name, url, gsurl, text})
}
return files
}
async function getText(docID) {
var docRef = firestore.collection("recipies").doc(docID);
let doc = await docRef.get()
if (doc.exists){return await doc.data().text}}
buildFiles().then(res=>this.files = res)
Thanks also to #cyqsimon and #Nav Kumar V

Async/Await Not Waiting as I expect it

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.

React Native AsyncStorage returns promise instead of value

I understand this is a very common problem in RN and I am still trying to understand the very possible advantage of returning a promise when loading data from a property file instead of just returning the value, which makes chaining requests very cumbersome...but anyway. Here is what I have right now, which is a wrapper from the AsyncStorage RN implementation:
multiGet = async (key) => {
var value = null;
try {
value = await AsyncStorage.multiGet(key).then(
(values) => {
value = values;
console.log('Then: ',values);
});
} catch (error) {
console.log('Error: ',error);
}
console.log('Final: ',value);
return value;
}
At this point, value gets undefined. In my main code I have this:
var filter = this._getFilter();
console.log('returned filter:',filter);
The _getFilter function is the one using the AsyncStorage wrapper but the 'returned filter' is logging before the first function so it is not waiting for the returned values before continue, so I get an undefined value.
At first, I thought that just by using the async/await the AsyncStorage wold return a value instead of a promise but after testing, the value I get from:
value = await AsyncStorage.getItem('key')
is STILL a promise, so I have to use then() to read the value.
Basically the order that I am seeing in the logs is:
_getFilter
returned value: undefined
Then: value: here I get the correct value from the keys but the code already passed and I don't have the correct value in the variable
I have no clue what is going on or how to handle this correctly. This is supposed to be very simple and common use case.
I would love to solve this without using a third party module.
Thanks
SOLUTION
Edit: After understanding a little more about the concepts of async/await and callbacks, I finally have a code that works. I don't like it, because it makes the code very hard to read. I might need to refactor it to use promises but for now, it works. Here are some snippets in case someone finds the same issue:
this._getFilter(body,this._filterSearchCallback,callback);
Note: I am sending the body through the chain because I am "completing" the information as I pass the functions. The second parameter is the first callback that actually makes a fetch query and the third callback is the return of the fetch function.
_getFilter(body,callback,returnCallback){
{...}
this._sh.multiGet(keysBanks).then(
(banks) => {
filter.banks = banks;
console.log(banks);
this._sh.multiGet(keysCards).then(
(cards) => {
console.log(cards);
filter.credit_cards = cards;
callback(body,filter,returnCallback);
});
}
);
}
Here basically I am chaining a couple of gets because I need several values from the store. This is the part I dont really like. _sh is my StorageHelper which is a wrapper to the AsyncStorage, nothing fancy.
multiGet = async (key) => {
const value = await AsyncStorage.multiGet(key);
return value;
}
Then my very last callback that actually makes the fetch and send the JSON response to the main screen in react native:
_filterSearchCallback(body,filter,callback){
body.filter = filter;
return fetch(apiUrl, {method: 'post', body: JSON.stringify(body)})
.then((response) => response.json())
.then((responseJson) => {
callback(responseJson);
})
.catch((error) => {
console.error(error);
callback(responseJson);
});
}
I will improve this and make it cleaner but for now, it works. Hope it helps others too.
Once upon a time, i was having the same problem so what I did I will share with you here.
Basically, your execution is moving forward without taking any value i.e undefined what you are getting right now so there are 3-4 ways to get out of this:
1) async await
2) callback
1) We will start with the callback which is use by most of the people.
We will use your code to implement this:
_getFilter(key,callback)
{
multiGet = (key) => {
var collect;
try {
var value = AsyncStorage.multiGet(key).then(
(values) => {
// value = values;
console.log('Then: ',values);
callback(values)
});
} catch (error) {
console.log('Error: ',error);
}
console.log('Final: ',value);
}
}
this._getFilter(key,function(filter){
console.log('returned filter:',filter);
});
2)async/await
If you are using await alone then you would get an error, to use await inside a function you have to declare the async function by setting async keyword before the function name.
async _getFilter(key)
{
multiGet = async (key) => {
var value,collect;
try {
value = await AsyncStorage.multiGet(key).then(
(values) => {
collect= values;
console.log('Then: ',values);
});
} catch (error) {
console.log('Error: ',error);
}
console.log('Final: ',value);
return collect;
}
//calling the async function
this._getFilter(key).then((filter)=>{
if(filter!=null)
console.log('returned filter:',filter)
else
console.log('error')
})
Hope this would clear your concepts and help you with other react native developers.I have seen lots of people struggling with this thing so today I got the chance to clear your doubts.
Cheers :)
the thing is await turns the promise into a value, you don't need to use .then(). Try the following:
const keys = await AsyncStorage.getAllKeys()
const values = await AsyncStorage.multiGet(keys)
// at this point `values` will have data for all keys

Categories

Resources