This question already has answers here:
what is right way to do API call in react js?
(14 answers)
async/await implicitly returns promise?
(5 answers)
Closed 26 days ago.
I have a promise from Fetch that I use to get the list of countries from our API:
async fetchDataFromAPI () {
console.log('fetchDataFromAPI')
let response = await fetch(process.env.NEXT_PUBLIC_BRIDGEMAN_API_URL + '/countries',
{
method: 'GET',
headers: {
'Content-Type': 'application/ld+json',
'Accept': 'application/json'
}
})
let data = await response.json();
return data
}
I call that promise inside of a class component in componentDidMount:
componentDidMount() {
this.setState({countries: this.fetchDataFromAPI()})
}
Then when I pass it to the render function, Like this:
const {
countries
} = this.state
console.log('state countries', countries)
I get the following output in my console:
Promise { <state>: "pending" }
<state>: "fulfilled"
<value>: Array(244) [ {…}, {…}, {…}, … ]
[0…99]
[100…199]
[200…243]
length: 244
<prototype>: Array []
<prototype>: Promise.prototype { … }
All the data is there in the ''. I just dont know how to get at it?
I've tried countries.then((data) => {console.log('data', data)}) but I get the message that countries has no method .then. I've tried countries.map and that is the same error.
How can I loop through and output the data on the page?
Via a microservice, I retrieve several packages of JSON data and spit them out onto a Vue.js-driven page. The data looks something like this:
{"data":{"getcompanies":
[
{"id":6,"name":"Arena","address":"12 Baker Street","zip":"15090"},
{"id":7,"name":"McMillan","address":null,"zip":"15090"},
{"id":8,"name":"Ball","address":"342 Farm Road","zip":"15090"}
]
}}
{"data":{"getusers":
[{"id":22,"name":"Fred","address":"Parmesean Street","zip":"15090"},
{"id":24,"name":"George","address":"Loopy Lane","zip":"15090"},
{"id":25,"name":"Lucy","address":"Farm Road","zip":"15090"}]}}
{"data":{"getdevices":
[{"id":2,"name":"device type 1"},
{"id":4,"name":"device type 2"},
{"id":5,"name":"device type 3"}]}}
...and I successfully grab them individually via code like this:
getCompanies() {
this.sendMicroServiceRequest({
method: 'GET',
url: `api/authenticated/function/getcompanies`
})
.then((response) => {
if(response.data) {
this.dataCompanies = response.data.getcompanies
} else {
console.error(response)
}
}).catch(console.error)
}
...with getUsers() and getDevices() looking respectively the same. getCompanies() returns:
[{"id":6,"name":"Arena","address":"12 Baker Street","zip":"15090"},
{"id":7,"name":"McMillan","address":null,"zip":"15090"},
{"id":8,"name":"Ball","address":"342 Farm Road","zip":"15090"}]
...which I relay to the Vue template in a table, and this works just fine and dandy.
But this is obviously going to get unwieldy if I need to add more microservice calls down the road.
What I'm looking for is an elegant way to jump past the response.data.*whatever* and get to those id-records with a re-useable call, but I'm having trouble getting there. response.data[0] doesn't work, and mapping down to the stuff I need either comes back undefined, or in bits of array. And filtering for response.data[0].id to return just the rows with ids keeps coming back undefined.
My last attempt (see below) to access the data does work, but looks like it comes back as individual array elements. I'd rather not - if possible - rebuild an array into a JSON structure. I keep thinking I should be able to just step past the next level regardless of what it's called, and grab whatever is there in one chunk, as if I read response.data.getcompanies directly, but not caring what 'getcompanies' is, or needing to reference it by name:
// the call
this.dataCompanies = this.getFullData('companies')
getFullData(who) {
this.sendMicroServiceRequest({
method: 'GET',
url: 'api/authenticated/function/get' + who,
})
.then((response) => {
if(response) {
// attempt 1 to get chunk below 'getcompanies'
Object.keys(response.data).forEach(function(prop) {
console.log(response.data[prop])
})
// attempt 2
// for (const prop in response.data) {
// console.log(response.data[prop])
// }
let output = response.data[prop] // erroneously thinking this is in one object
return output
} else {
console.error(response)
}
}).catch(console.error)
}
...outputs:
(63) [{…}, {…}, {…}] <-- *there are 63 of these records, I'm just showing the first few...*
0: {"id":6,"name":"Arena","address":"12 Baker Street","zip":"15090"}
1: {"id":7,"name":"McMillan","address":null,"zip":"15090"},
2: {"id":8,"name":"Ball","address":"342 Farm Road","zip":"15090"}...
Oh, and the return above comes back 'undefined' for some reason that eludes me at 3AM. >.<
It's one of those things where I think I am close, but not quite. Any tips, hints, or pokes in the right direction are greatly appreciated.
I feel it's better to be explicit about accessing the object. Seems like the object key is consistent with the name of the microservice function? If so:
getData(functionName) {
return this.sendMicroServiceRequest({
method: 'GET',
url: "api/authenticated/function/" + functionName
})
.then( response => response.data[functionName] )
}
getCompanies(){
this.getData("getcompanies").then(companies => {
this.dataCompanies = companies
})
}
let arrResponse = {data: ['x']};
let objResponse = {data: {getcompanies: 'x'}};
console.log(arrResponse.data[0]);
console.log(Object.values(objResponse.data)[0]);
response.data[0] would work if data was an array. To get the first-and-only element of an object, use Object.values(response.data)[0] instead. Object.values converts an object to an array of its values.
Its counterparts Object.keys and Object.entries likewise return arrays of keys and key-value tuples respectively.
Note, order isn't guaranteed in objects, so this is only predictable in your situation because data has exactly a single key & value. Otherwise, you'd have to iterate the entry tuples and search for the desired entry.
firstValue
Let's begin with a generic function, firstValue. It will get the first value of an object, if present, otherwise it will throw an error -
const x = { something: "foo" }
const y = {}
const firstValue = t =>
{ const v = Object.values(t)
if (v.length)
return v[0]
else
throw Error("empty data")
}
console.log(firstValue(x)) // "foo"
console.log(firstValue(y)) // Error: empty data
getData
Now write a generic getData. We chain our firstValue function on the end, and be careful not to add a console.log or .catch here; that is a choice for the caller to decide -
getData(url) {
return this
.sendMicroServiceRequest({ method: "GET", url })
.then(response => {
if (response.data)
return response.data
else
return Promise.reject(response)
})
.then(firstValue)
}
Now we write getCompanies, getUsers, etc -
getCompanies() {
return getData("api/authenticated/function/getcompanies")
}
getUsers() {
return getData("api/authenticated/function/getusers")
}
//...
async and await
Maybe you could spruce up getData with async and await -
async getData(url) {
const response =
await this.sendMicroServiceRequest({ method: "GET", url })
return response.data
? firstValue(response.data)
: Promise.reject(response)
}
power of generics demonstrated
We might even suggest that these get* functions are no longer needed -
async getAll() {
return {
companies:
await getData("api/authenticated/function/getcompanies"),
users:
await getData("api/authenticated/function/getusers"),
devices:
await getData("api/authenticated/function/getdevices"),
// ...
}
}
Above we used three await getData(...) requests which happen in serial order. Perhaps you want all of these requests to run in parallel. Below we will show how to do that -
async getAll() {
const requests = [
getData("api/authenticated/function/getcompanies"),
getData("api/authenticated/function/getusers"),
getData("api/authenticated/function/getdevices")
]
const [companies, users, devices] = Promise.all(requests)
return { companies, users, devices }
}
error handling
Finally, error handling is reserved for the caller and should not be attempted within our generic functions -
this.getAll()
.then(data => this.render(data)) // some Vue template
.catch(console.error)
I've seen some similar questions but they don't seem to match my situation.
When I log this.$store.state.account I get the expected result
{__ob__: Nt}
user: Object
favorites_playlist: (...)
firebaseID: (...)
spotifyID: (...)
However, when I console.log(this.$store.state.account.user) the user object disappears! All of the nested properties inside user return undefined though they log perfectly fine above
console.log(this.$store.state.account.user)
{__ob__: Nt}
__ob__: Nt {value: {…}, dep: vt, vmCount: 0}
__proto__: Object
This is the method inside my component calling the object
async connectToSpotify() {
console.log("User Profile: ", this.user)
var firebaseID = await this.$store.dispatch("signInToFirebase")
var authID = await this.$store.dispatch('connectToSpotify')
Vue.set(this.$store.state.spotify, "authId", authID)
var userProfile = await this.$store.dispatch("fetchUserDataFromSpotify", authID)
userProfile["firebaseID"] = firebaseID
this.$store.dispatch("bindCurrentUser", userProfile.spotifyID)
console.log("this.$store.state")
console.log(this.$store.state);
console.log("this.$store.state.account")
console.log(this.$store.state.account);
console.log("this.$store.state.account.user")
console.log(this.$store.state.account.user);
console.log("this.$store.state.account.user.favorites_playlist")
console.log(this.$store.state.account.user.favorites_playlist);
// console.log(this.$store.state.account.user.firebaseID);
var favorites_playlist = this.$store.state.account.user.favorites_playlist
var playlistID = await this.$store.dispatch("createFavoritesPlaylist", [authID, userProfile.spotifyID, favorites_playlist])
console.log(`PlaylistID: ${playlistID}`);
userProfile["favorites_playlist"] = playlistID
console.log(this.$store);
return db.ref(`users/${userProfile.spotifyID}`).update(userProfile)
},
this is the action inside my accounts module that binds the user to firebase
const state = () => ({
//user: { voted_tracks: {}, favorited_tracks: {}, favorites_playlist: null, authID: null}
user: {},
})
const actions = {
bindCurrentUser: firebaseAction(({ bindFirebaseRef }, id) => {
return bindFirebaseRef('user', db.ref('users').child(id))
}),
}
Not sure what further information would be relevant aside that this.$store.state.account.user is binded via Vuexfire to a database reference. The store is injected into the root component
Your data comes in after the console.log. The console updates object/array logs with current values when you click, but can't do that with primitives. See this answer for more detail.
It should be enough to await the firebaseAction:
await this.$store.dispatch("bindCurrentUser", userProfile.spotifyID)
bindCurrentUser: firebaseAction(({ bindFirebaseRef }, id) => {
console.log("account.bindCurrentUser() called");
return bindFirebaseRef('user', db.ref(`users/${id}`)).then(() => {
console.log("account.bindCurrentUser() -- complete")
}).catch((err) => {console.log(err)})
}),
This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 3 years ago.
I am running a server that is serving a Vue.js app.
So if I input http://localhost:9999/ into my browser,
the browser gets 4 important files:
post.js, get.js, vue.js and the index.HTML with the vue code.
I got a dynamic ordered list to work where each list element
has a button to add an element and to remove itself as well as
a debug button which outputs some variables to the console.
Now I need to make a get request to my server to get an Array with JSON data
that will create some elements in a second ordered list.
I tried the following but nothing works:
//get("http://localhost:9999/text/1", inputData)
//get("http://localhost:9999/text/1").then(inputData)
//inputData = get("http://localhost:9999/text/1")
This is the content of get.js:
//which is correctly included in the vue.js index.HTML
//<script SRC="get.js"> </script>
function get(url, params) {
// Return a new promise.
return new Promise(function(resolve, reject) {
// Do the usual XHR stuff
var req = new XMLHttpRequest();
req.open('GET', url, true);
req.setRequestHeader('Content-type', 'application/json');
req.onload = function() {
// This is called even on 404 etc
// so check the status
if (req.status == 200) {
// Resolve the promise with the response text
//resolve(req.response);
resolve(JSON.parse(req.response));
}
else {
// Otherwise reject with the status text
// which will hopefully be a meaningful error
reject(req.statusText);
}
};
// Handle network errors
req.onerror = function() {
reject("Network Error");
};
// Make the request
req.send(params);
});
}
After the vue.js method block I call
mounted() {
this.$nextTick(function () {
var inputData=[]
//get("http://localhost:9999/text/1", inputData)
//get("http://localhost:9999/text/1").then(inputData)
inputData = get("http://localhost:9999/text/1")
app.vueData = inputData
console.log(inputData)
console.log(JSON.stringify(inputData))
console.log(';)')
})
}
The Promise has the content but I can't transfer it to the variable.
Promise {<pending>}
__proto__: Promise
catch: ƒ catch()
constructor: ƒ Promise()
finally: ƒ finally()
then: ƒ then()
Symbol(Symbol.toStringTag): "Promise"
__proto__: Object
[[PromiseStatus]]: "resolved"
[[PromiseValue]]: Array(4)
0: {text: "This List"}
1: {text: "was pulled"}
2: {text: "from the"}
3: {text: "server"}
length: 4
__proto__: Array(0)
Since comments get deleted I have to get creative:
#Apal Shah
Thanks for this answer. Your code looks way better than the then() solution.
I got wind of the culprit before reading your solution by adding a lot of console.log()s
console.log('app.vueData vor app.vueData = inputData: ')
console.log(app.vueData)
app.vueData = inputData
console.log('inputData nach Zuweisung: ')
console.log(inputData)
console.log('JSON.stringify(inputData)')
console.log(JSON.stringify(inputData))
console.log(';)')
Console Output:
get block: (index):153
app.vueData vor app.vueData = inputData: (index):156
[__ob__: Observer] (index):157
length: 0
__ob__: Observer {value: Array(0), dep: Dep, vmCount: 0}
__proto__: Array
inputData nach Zuweisung: (index):161
[__ob__: Observer] (index):162
length: 0
__ob__: Observer {value: Array(0), dep: Dep, vmCount: 0}
__proto__: Array
JSON.stringify(inputData) (index):164
[] (index):165
;) (index):167
Download the Vue Devtools extension for a better development experience: vue.js:9049
https://github.com/vuejs/vue-devtools
You are running Vue in development mode. vue.js:9058
Make sure to turn on production mode when deploying for production.
See more tips at https://vuejs.org/guide/deployment.html
(4) [{…}, {…}, {…}, {…}] (index):154
0: {text: "This List"}
1: {text: "was pulled"}
2: {text: "from the"}
3: {text: "server"}
length: 4
__proto__: Array(0)
Thanks a bunch going to test it now.
The solution is:
mounted() {
this.$nextTick(async function () {
console.log('get block: ')
console.log('app.vueData vor app.vueData = get() ')
console.log(app.vueData)
//Get is a deferred / asynchronous process / operation
app.vueData = await get("http://localhost:9999/text/1")
console.log('app.vueData nach Zuweisung: ')
console.log(app.vueData)
console.log('JSON.stringify(app.vueData)')
console.log(JSON.stringify(app.vueData))
})
console.log(';)')
}
The caveat was that async had to go in front of function not mounted or this.$nextTick .
You have created a Promise which resolves the data after the completion of your HTTP request but your code is not waiting for this promise to resolve.
You can do 2 things:
1. Use .then()
2. Use async/await (I prefer this because it looks clean)
If you want to use async/await,
mounted() {
this.$nextTick(async function () {
var inputData=[]
//get("http://localhost:9999/text/1", inputData)
//get("http://localhost:9999/text/1").then(inputData)
inputData = await get("http://localhost:9999/text/1")
app.vueData = inputData
console.log(inputData)
console.log(JSON.stringify(inputData))
console.log(';)')
})
}
In this code, you can see that there is async keyword before the function inside this.$nextTick and await keyword before your get method.
If you want to handle the error, you can always use try/catch block.
try {
inputData = await get("http://localhost:9999/text/1");
} catch (e) {
console.log(e);
}
If you're a fan of .then(), your mounted method would look something like this,
mounted() {
this.$nextTick(function () {
var inputData=[]
get("http://localhost:9999/text/1").then(inputData => {
app.vueData = inputData
console.log(inputData)
console.log(JSON.stringify(inputData))
});
console.log(';)')
})
}
Good morning !
Currently I am trying to create some kind of simple validation using javascript, but I have a problem with using async functionalities.
Below you can see UI method which iterates through validityChecks collections
checkValidity: function(input){
for(let i = 0; i<this.validityChecks.length; i++){
var isInvalid = this.validityChecks[i].isInvalid(input);//promise is returned
if(isInvalid){
this.addInvalidity(this.validityChecks[i].invalidityMessage);
}
var requirementElement = this.validityChecks[i].element;
if(requirementElement){
if(isInvalid){
requirementElement.classList.add('invalid');
requirementElement.classList.remove('valid');
}else{
requirementElement.classList.remove('invalid');
requirementElement.classList.add('valid');
}
}
}
}
Below is specific collection object which is not working as it was intended
var usernameValidityChecks = [
{
isInvalid: function(input){
var unwantedSigns = input.value.match(/[^a-zA-Z0-9]/g);
return unwantedSigns ? true:false;
},
invalidityMessage:'Only letters and numbers are allowed',
element: document.querySelector('.username-registration li:nth-child(2)')
},
{
isInvalid: async function (input){
return await checkUsername(input.value);
},
invalidityMessage: 'This value needs to be unique',
element: document.querySelector('.username-registration li:nth-child(3)')
}
]
function checkUsername (username) {
return new Promise ((resolve, reject) =>{
$.ajax({
url: "check.php",
type: "POST",
data: { user_name: username },
success: (data, statusText, jqXHR) => {
resolve(data);
},
error: (jqXHR, textStatus, errorThrown) => {
reject(errorThrown);
}
});
});
}
The problem is that to var isInvalid in checkValidity method is returned promise. Can anyone advice how to returned there value instead of promise ? Or how to handle promise there ?
Thank you in advance!
EDIT :
Forgive me, but it is my first time with Stack and probably my question is a little bit inaccurate. Some of objects in collections are also synchronous, I changed usernameValidityChecks. How to handle this kind of situation ? Where I have async and sync method at the same time ?
Instead of below line
var isInvalid = this.validityChecks[i].isInvalid(input);
you can use below code to make code much cleaner and store the desired value on the variable however above suggested solutions are also correct
var isInvalid = await this.validityChecks[i].isInvalid(input).catch((err) => { console.log(err) });
After this line you will get the desired value on isInvalid variable
And also add async keyword in all other isInvalid function definition
Use .then to access the result of a promise:
var isInvalid = this.validityChecks[i].isInvalid(input).then(res => res);