Javascript async function throws error when using await - javascript

I'm still beginner when it comes to JavaScript.
In my vuejs3 app I have the following situation (I assume knowing this is Vue is not important):
In my Vue app I have a method like this
async LoadBasicData()
{
this.basicData = await window.LoadBasicData();
},
It calls a function in a different JavaScript file. The method looks a bit like this:
async function LoadBasicData()
{
return new Promise(resolve =>
{
let result = Object.create(null);
let url = 'MyFancyUrl';
axios
.get(url)
.then((response) =>
{
result.data = response.data;
result = await LoadBuildId(result);
resolve(result);
})
});
}
The LoadBuildId function is looking very similar
async function LoadBuildId(result)
{
return new Promise(resolve =>
{
let url = 'MySecondFancyUrl';
axios
.get(url)
.then((response) =>
{
result.buildId = response.data;
resolve(result);
})
});
}
From my understanding of all this async/await/promise this should be the correct way of doing it, but considering that it's not working I'm pretty sure there is something I have misunderstood
I'm getting the error "await is only valid in async functions and the top level bodies of modules" when calling LoadBuildId. I don't understand the error, as await is called in an async function
What am I doing wrong here? Why can't my async function call another async function? Or is there a better way?
I know there is at least one way to accomplish what I need, that is by using callbacks. I have been using it until now, but I'm not really a fan of it as it really makes the code unnecessary complicated.

You're making 2 mistakes
Wrapping the existing promise-related functionality with your own promise (see: What is the explicit promise construction antipattern and how do I avoid it?)
Mixing async/await with then
The code can be hugely simplified:
async function LoadBuildId(result)
{
let url = 'MySecondFancyUrl';
const response = await axios.get(url)
result.buildId = response.data;
return result;
}
and
async function LoadBasicData()
{
let url = 'MyFancyUrl';
let result = Object.create(null);
const response = await axios.get(url);
result = await LoadBuildId(result);
return result;
}
To be honest, it can be simplified even further - theres no need to be constructing result objects just to set properties on it - you may as well just return the results from a call to axios.get

Related

How to consume a promise and use the result later on with my code?

I'm new to async operations and js. Here is my question.
I have a Person class. I want to init Person instance using data that I get from an API call.
class Person {
constructor(data) {
this.data = data;
}
}
I am making an API call using Axios. I get a response and want to use it in my class.
const res = axios.get('https://findperson.com/api/david');
const david = new Person(res);
I understand that res is a promise at this stage, and that I need to consume it.
How should I do it?
How can I take the response and use it properly?
axios.get() return a promise of an object which contains the returned data, status, headers, etc...
async function getPerson() {
try {
const res = await axios.get('https://findperson.com/api/david');
const david = new Person(res.data);
// do something with david
} catch (error) {
console.log(error)
}
}
or
function getPerson() {
axios
.get('https://findperson.com/api/david')
.then(res => {
const david = new Person(res.data)
// do something with david
})
.catch(console.log)
}
Inside another async function, or at the top level of a module or at the REPL (in node 16.6+ or some earlier versions with the --experimental-repl-await feature enabled), you can just use await.
const res = await axios.get('https://findperson.com/api/david');
That will wait for the promise to be resolved and unwrap it to store the contained value in res.
If you want to get the value out of the world of async and into synchro-land, you have to do something with it via a callback function:
axios.get('https://findperson.com/api/david').then(
res => {
// do stuff with res here
});
... but don't be fooled; without an await, any code that comes after that axios.get call will run immediately without waiting for the callback. So you can't do something like copy res to a global var in the callback and then expect it to be set in subsequent code; it has to be callbacks all the way down.
You can do this:
axios.get('https://findperson.com/api/david').then(res => {
const david = new Person(res);
});
Or in async function: (See async await for javascript)
const res = await axios.get('https://findperson.com/api/david');
const david = new Person(res);

How to await on async function and collect the response

Until now, I thought await makes my program synchronous. However, I see that await only waits for async function to be resolved with a promise then the whole programs continues to run. So, what is the right way to wait & collect the response from async function?
Original code:
let result={
name:'',
country:''
};
const data = await query.getCachedData(did);
result.name = data.name; // undefined
result.country = data.country;//undefined
console.log(result);
I don't know why but awaiting on the async function result works:
let result={
name:'',
country:''
};
const data = await query.getCachedData(did);
result.name = await data.name; //'Jon'
result.country = await data.country;// 'US'
console.log(result);
But I am not sure if this is the solution.
Since getCachedData returns the promise, I thought this may be the right way but the then()/catch() didn't execute.
query.getCachedData(did).then((tfnData) => {
result.name = data.name;
result.country = data.country;
console.log(result);
}).catch((dbError) => {
console.log(dbError);
});
Can anyone correct me to get the result the right way?
A Promise is the return from a async function. The result is maybe not finish yet.
That is why you can await a method (like you did it). This will set the return from the function when the calculation is complied.
Or you can make use of 'then':
const data1 = await query.getCachedData(did);
//data1 has a value at this point of code
const data2;
query.getChachedData(did).then((result)=>{data2 = result});
//data2 can have a value or will get one later (this is async) at this point of code
With Promise all you can let multiple methods run asynchronous and wait for all at once.
https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
const callStack = [];
const data1;
const data2;
callStack.push(query.getChachedData(did).then((result)=>{data1 = result}));
callStack.push(query.getChachedData(did).then((result)=>{data2 = result}));
//run both query Methods at asynchronous
await Promise.all(callStack);
//data1 and data2 hava a value at this point of code
Until now, I thought await makes my program synchronous
Async/await makes the code to looks like synchronous, but behind is just syntactic sugar for promises in Javascript. Really? Yes
Is just return thePromise().then(result => result)
I see that await only waits for async function to be resolved with a promise then the whole programs continues to run
When you work with promises, they not make Node.js code run synchronous, in the other hand, promises allow you to write flows that appear synchronous.
So, what is the right way to wait & collect the response from async function?
According to your example, the code will be something like this:
const queryResult = did => query.getCachedData(did).then(tfnData => tfnData);
// Without async/await
queryResult(did)
.then(data => {
const { name, country } = data;
const result = { name, country };
console.log(`${result.name}, ${result.country}`); // Jon, US
})
.catch(error => console.log(`Error produced: ${error}`));
// With async/await
(async () => {
try {
// ... Some other code ....
// ... Some other code ....
// ... Some other code ....
const data = await queryResult(did);
const { name, country } = data;
const result = { name, country };
console.log(`${result.name}, ${result.country}`); // Jon, US
} catch (error) {
console.log(`Error inside try-catch: ${error}`);
}
})();
You are correct that await waits for an async function to return a promise. For your code example, I would suggest the following:
let result={
name:'',
country:''
};
async function getData() {
const data = await query.getCachedData(did);
result.name = data.name; // 'Jon'
result.country = data.country; // 'US'
console.log(result);
}
await can only be used inside an async function. It will pause the function until a promise is received from query.getCachedData(), save that response into const data which can then be used to set the name and country of the result object. You can also look at the MDN docs for both async and await.
await does not make your asynchronous code synchronous - and there should not be any reasonable reason to do so ... behind the scenes it returns a promise and chain it with then instead of you having to chain it with then yourself.
What you did with the then keyword, is what the await did for you.
You can use whatever suits you, but async/await makes your code easier to read.
if this works
result.name = await data.name;
this means that the name is an async getter function that you have to await for it to get the result. You can do it also like this
data.name.then(name => doWhateverYouWantWithName(name)) or using the await keyword like you already did - and that should be better even.

Better way to deal with Promise flow when functions need to access outer scope variable

I'm creating a Node.js module to interact with my API, and I use the superagent module to do the requests. How it works:
module.exports = data => {
return getUploadUrl()
.then(uploadFiles)
.then(initializeSwam)
function getUploadUrl() {
const request = superagent.get(....)
return request
}
function uploadFiles(responseFromGetUploadUrl) {
const request = superagent.post(responseFromGetUploadUrl.body.url)
// attach files that are in data.files
return request
}
function initializeSwam(responseFromUploadFilesRequest) {
// Same thing here. I need access data and repsonseFromUploadFilesRequest.body
}
}
I feel like I'm doing something wrong like that, but I can't think in a better way to achieve the same result.
Two simple ways:
write your function to take all parameters it needs
const doStuff = data =>
getUploadUrl()
.then(uploadFiles)
.then(initializeSwam)
might become
const doStuff = data =>
getUploadUrl()
.then(parseResponseUrl) // (response) => response.body.url
.then(url => uploadFiles(data, url))
.then(parseResponseUrl) // same function as above
.then(url => initializeSwam(data, url))
That should work just fine (or fine-ish, depending on what hand-waving you're doing in those functions).
partially apply your functions
const uploadFiles = (data) => (url) => {
return doOtherStuff(url, data);
};
// same deal with the other
const doStuff = data =>
getUploadUrl()
.then(parseResponseUrl)
.then(uploadFiles(data)) // returns (url) => { ... }
.then(parseResponseUrl)
.then(initializeSwam(data));
A mix of all of these techniques (when and where sensible) should be more than sufficient to solve a lot of your needs.
The way you have your code structured in the above snippet results in the getUploadUrl(), uploadFiles(), and initializeSwam() functions not being declared until the final .then(initializeSwam) call is made. What you have in this final .then() block is three function declarations, which simply register the functions in the namespace in which they are declared. A declaration doesn't fire-off a function.
I believe what you want is something like:
async function getUploadUrl() { <-- notice the flow control for Promises
const request = await superagent.get(....);
return request;
}
async function uploadFiles(responseFromGetUploadUrl) {
const request = await superagent.post(responseFromGetUploadUrl.body.url)
// attach files that are in data.files
return request;
}
async function initializeSwam(responseFromUploadFilesRequest) {
// Same thing here. I need access data and
repsonseFromUploadFilesRequest.body
const request = await ...;
}
module.exports = data => {
return getUploadUrl(data) <-- I'm guessing you wanted to pass this here
.then(uploadFiles)
.then(initializeSwam);
}
This approach uses ES6 (or ES2015)'s async/await feature; you can alternatively achieve the same flow control using the bluebird Promise library's coroutines paired with generator functions.

How to get data returned from fetch() promise?

I am having trouble wrapping my head around returning json data from a fetch() call in one function, and storing that result in a variable inside of another function. Here is where I am making the fetch() call to my API:
function checkUserHosting(hostEmail, callback) {
fetch('http://localhost:3001/activities/' + hostEmail)
.then((response) => {
response.json().then((data) => {
console.log(data);
return data;
}).catch((err) => {
console.log(err);
})
});
}
And this is how I am trying to get the returned result:
function getActivity() {
jsonData = activitiesActions.checkUserHosting(theEmail)
}
However, jsonData is always undefined here (which I am assuming is because the async fetch call has not finished yet when attempting to assign the returned value to jsonData. How do I wait long enough for the data to be returned from the fetch call so that I can properly store it inside of jsonData?
always return the promises too if you want it to work:
- checkUserHosting should return a promise
- in your case it return a promise which return the result data.
function checkUserHosting(hostEmail, callback) {
return fetch('http://localhost:3001/activities/' + hostEmail)
.then((response) => {
return response.json().then((data) => {
console.log(data);
return data;
}).catch((err) => {
console.log(err);
})
});
}
and capture it inside .then() in your main code:
function getActivity() {
let jsonData;
activitiesActions.checkUserHosting(theEmail).then((data) => {
jsonData = data;
}
}
EDIT:
Or even better, use the new syntax as #Senthil Balaji suggested:
const checkUserHosting = async (hostEmail, callback) => {
let hostEmailData = await fetch(`http://localhost:3001/activities/${hostEmail}`)
//use string literals
let hostEmailJson = await hostEmailData.json();
return hostEmailJson;
}
const getActivity = async () => {
let jsonData = await activitiesActions.checkUserHosting(theEmail);
//now you can directly use jsonData
}
You're partially right. It's because you're trying to get the result of this asynchronous call in a synchronous fashion. The only way to do this is the same way you deal with any other promise. Via a .then callback. So for your snippet:
function getActivity() {
return activitiesActions.checkUserHosting(theEmail).then((jsonData) => {
// Do things with jsonData
})
}
Any function that depends on an asynchronous operation must itself become asynchronous. So there's no escaping the use of .then for anything that requires the use of the checkUserHosting function.
You can make use of new ES6 and Es7 syntax and what others have written is also correct, but this can be more readable and clean,
you are trying to get aysnc value synchronously, here jsonData will be undefined because, you move to next line of execution before async function(checkUserHosting) is finish executing, this can be written as follows
const getActivity = async () => {
let jsonData = await activitiesActions.checkUserHosting(theEmail);
//now you can directly use jsonData
}
and you can write checkUserHosting in a different using new syntax like this
const checkUserHosting = async (hostEmail, callback) => {
let hostEmailData = await fetch(`http://localhost:3001/activities/${hostEmail}`)
//use string literals
let hostEmailJson = await hostEmailData.json();
return hostEmailJson;
}

await Promises.all SyntaxError

According to this article: https://medium.com/#bluepnume/learn-about-promises-before-you-start-using-async-await-eb148164a9c8
It seems like it could be possible to use below syntax:
let [foo, bar] = await Promise.all([getFoo(), getBar()]);
for multiple promises execution. However while using it I get Uncaught SyntaxError: Unexpected identifier.
How can i use async/await and promise.all to achieve multiple simultaneous operations executed and one resolve with a response.
-----EDITED
the function i am using inside promise.all is this one:
async function getJson(callback) {
try {
let response = await fetch('URL_LINK_HERE');
let json = await response.json();
return json;
} catch(e) {
console.log('Error!', e);
}
}
as a test field i am using google chrome Version 60.0.3112.113
Most likely your code looks something like this:
var thingsDone = await Promise.all([
Promise.resolve("eat"),
Promise.resolve("sleep")
]);
console.log(thingsDone);
This will not work because the await keyword is only valid within an async function (which the global context is not). It will simply cause a syntax error.
One way to handle this is to use it like a regular old promise and not using the await keyword:
Promise.all([
Promise.resolve("eat"),
Promise.resolve("sleep")
]).then((thingsDone) => console.log(thingsDone));
Or if you want to get fancy (or need more room to write an expressive function), wrap your logic in an async function and then handle it like a promise:
async function doThings() {
var eat = await Promise.resolve("eat");
var sleep = await Promise.resolve("sleep");
return Promise.all([Promise.resolve(eat), Promise.resolve(sleep)]);
}
doThings().then((thingsDone) => console.log(thingsDone));
This would allow you to use await as needed and is much more helpful in a more complicated function.
Or even more succinctly using an immediately-executing async function:
(async() => {
var eat = await Promise.resolve("eat");
var sleep = await Promise.resolve("sleep");
return Promise.all([Promise.resolve(eat), Promise.resolve(sleep)]);
})().then((thingsDone) => console.log(thingsDone));
torazaburo pointed me to right direction in his comments and i figured it out, this is the final code that is working:
var getJson = async function() {
try {
let response = await fetch('http://mysafeinfo.com/api/data?list=englishmonarchs&format=json');
let json = await response.json();
return json;
} catch(e) {
console.log('Error!', e);
}
}
var check_all = async function(callback) {
callback( [foo, bar] = await Promise.all([getJson(), getJson()]) );
};
check_all(function(data) {
console.log(data);
});
This works, example here: https://jsfiddle.net/01z0kdae/1/

Categories