Reputation: 631
I have a React component that triggers an event to fetch data. This results in a dynamic number of stored proc calls to fetch data, and the data from each call is stored in a totally different location. Then I need to re-render once all of the data is received and available. I'm using promises with axios.
Since the number of axios calls is dynamic, I'm building an array and inserting it into axios.all
as follows:
let promises = [];
for (let i = 0; i < requests.length; i++) {
promises.push(axios.get(request[i].url, { params: {...} }));
}
axios.all(promises).then(/* use the data */);
The problem is that each axios request returns data that gets added to an object in a totally different place. Since I have no way to put them all in the correct place in a single then
(how would I know which response goes in which location?), I tried doing something like this:
let promises = [];
for (let i = 0; i < requests.length; i++) {
promises.push(
axios.get(request[i].url, { params: {...} })
.then(response => {myObject[request[i].saveLocation] = response.data;})
);
}
axios.all(promises).then(/* use the data */);
However, this doesn't work as I expected. The then
after each get
is executed, but not until well after the then
attached to axios.all
. Obviously this is a problem because my code tries to use the data before it has been saved to the object.
Is there a way to have a separate then
call for each axios.get
that will be executed after its corresponding promise is resolved, and then have a final then
that will be executed only after all of the promises are resolved, to use the data now that the object has been populated?
Upvotes: 18
Views: 49944
Reputation: 350272
If the behaviour of your second attempt is indeed like that, then that would be an indication that axios
is not Promise/A+ compliant. The then
callback's return value must be the value with which the promise returned by that then
is fulfilled. Since that is the promise you push into the array, the value that axios.all
would return for that promise can only be known by executing the then
callbacks first.
Even though you do not return a value explicitly in the then
callback, this does not affect the above rule: in that case the return value is undefined
and it is that value that should be provided by axios.all
once the corresponding promise is resolved.
See in particular the rules 2.2.7, 2.2.7.1, 2.3.2.1, 2.3.2.2 in the specs of Promise/A+:
2.2.7
then
must return a promise.promise2 = promise1.then(onFulfilled, onRejected);
2.2.7.1 If either
onFulfilled
oronRejected
returns a valuex
, run the Promise Resolution Procedure[[Resolve]](promise2, x)
.[...]
To run
[[Resolve]](promise, x)
, perform the following steps:[...]
2.3.2 If
x
is a promise, adopt its state:2.3.2.1 If
x
is pending,promise
must remain pending untilx
is fulfilled or rejected.2.3.2.2 If/when
x
is fulfilled, fulfillpromise
with the same value.
So I would suggest using a Promise/A+ compliant promise implementation instead. There are several other libraries, like for instance request-promise.
Alternatively, you could use the native ES6 Promise implementation, and promisify the http.request
method yourself.
ES6 offers Promise.all
which guarantees to provide the resolved values in the same order as the promises were provided.
Upvotes: 3
Reputation: 5687
It seems at this post day, axios recommends using Promise.all instead of axios.all https://github.com/axios/axios this is what worked for me, with Nuxtjs
async nuxtServerInit(vuexContext, context) {
console.log(context);
const primaryMenuData = {
query: `query GET_MENU($id: ID!) {
menu(id: $id, idType: NAME) {
count
id
databaseId
slug
name
menuItems {
edges {
node {
url
label
target
}
}
}
}
}`,
variables: {
"id": "Primary"
}
}
const primaryMenuOptions = {
method: 'POST',
headers: { 'content-type': 'application/json' },
data: primaryMenuData,
url: 'http://localhost/graphql'
};
const postsData = {
query: `query GET_POSTS($first: Int) {
posts(first: $first) {
edges {
node {
postId
title
date
excerpt
slug
author {
node {
name
}
}
featuredImage {
node {
altText
caption
sourceUrl(size: MEDIUM)
}
}
}
}
}
}`,
variables: {
"first": 15
}
}
const postsOptions = {
method: 'POST',
headers: { 'content-type': 'application/json' },
data: postsData,
url: 'http://localhost/graphql'
};
try {
const [primaryMenuResponse, postsResponse] = await Promise.all([
await axios(primaryMenuOptions),
await axios(postsOptions)
])
vuexContext.commit('setPrimaryMenu', primaryMenuResponse.data.data.menu.menuItems.edges);
vuexContext.commit('setPosts', postsResponse.data.data.posts.edges);
} catch (error) {
console.error(error);
}
},
Upvotes: 2
Reputation: 72
your initial code could work normally as intended if you pass the promises to your array attached with their respective then
function
let promises = []; // array to hold all requests promises with their then
for (let i = 0; i < requests.length; i++) {
// adding every request to the array
promises.push(
axios.get(request[i].url, { params: { ...} })
.then(response => { myObject[request[i].saveLocation] = response.data; })
);
}
// Resolving requests with their callbacks before procedding to the last then callback
axios.all(promises).then(/* use the data */);
Upvotes: -1
Reputation: 631
Okay, so I found a way to do what I needed without using using a then
on each get
. Since the params passed in to axios.get
contain enough info to determine the save location, and since I can read the params back from the response, I can do something like the following:
let promises = [];
for (let i = 0; i < requests.length; i++) {
promises.push(axios.get(request[i].url, { params: {...} }));
}
axios.all(promises)
.then(axios.spread((...args) => {
for (let i = 0; i < args.length; i++) {
myObject[args[i].config.params.saveLocation] = args[i].data;
}
}))
.then(/* use the data */);
This ensures all the data is received and saved to the object before it is used.
Upvotes: 34