Axios: chaining multiple API requests

I need to chain a few API requests from the Google Maps API, and I'm trying to do it with Axios.

Here is the first request, which is in componentWillMount()

axios.get('https://maps.googleapis.com/maps/api/geocode/json?&address=' + this.props.p1)
.then(response => this.setState({ p1Location: response.data }))  }

Here is the second request:

axios.get('https://maps.googleapis.com/maps/api/geocode/json?&address=' + this.props.p2)
.then(response => this.setState({ p2Location: response.data }))

Then we have a third request, which is dependent on the first two being completed:

axios.get('https://maps.googleapis.com/maps/api/directions/json?origin=place_id:' + this.state.p1Location.results.place_id + '&destination=place_id:' + this.state.p2Location.results.place_id + '&key=' + 'API-KEY-HIDDEN')
.then(response => this.setState({ route: response.data }))

How can I chain these three calls so that the third happens after the first two?

109565 次浏览

I think you need something like this:

const firstRequest = axios.get('https://maps.googleapis.com/maps/api/geocode/json?&address=' + this.props.p1)
.then(response => this.setState({ p1Location: response.data }))  }


const secondRequest = axios.get('https://maps.googleapis.com/maps/api/geocode/json?&address=' + this.props.p2)
.then(response => this.setState({ p2Location: response.data }))


const thirdRequest = axios.get('https://maps.googleapis.com/maps/api/directions/json?origin=place_id:' + this.state.p1Location.results.place_id + '&destination=place_id:' + this.state.p2Location.results.place_id + '&key=' + 'API-KEY-HIDDEN')
.then(response => this.setState({ route: response.data }))




Promise.all([firstRequest, secondRequest])
.then(() => {
return thirdRequest
})

This is related to JS's Promises. You can solve it in different ways. The simplest way to me is that you should nest each request starting from first to third. That means starting from the first request, you should put your second axios.get(url) into the first request's .then() and put the third request into the second request's .then().

For promises in general you expect that inside the .then() part promise is resolved and you can have access to the response. So that by nesting, you can solve the problem of being asynchronous in a not so elegant way.

First off, not sure you want to do this in your componentWillMount, it's better to have it in componentDidMount and have some default states that will update once done with these requests. Second, you want to limit the number of setStates you write because they might cause additional re-renders, here is a solution using async/await:

async componentDidMount() {


// Make first two requests
const [firstResponse, secondResponse] = await Promise.all([
axios.get(`https://maps.googleapis.com/maps/api/geocode/json?&address=${this.props.p1}`),
axios.get(`https://maps.googleapis.com/maps/api/geocode/json?&address=${this.props.p2}`)
]);


// Make third request using responses from the first two
const thirdResponse = await axios.get('https://maps.googleapis.com/maps/api/directions/json?origin=place_id:' + firstResponse.data.results.place_id + '&destination=place_id:' + secondResponse.data.results.place_id + '&key=' + 'API-KEY-HIDDEN');


// Update state once with all 3 responses
this.setState({
p1Location: firstResponse.data,
p2Location: secondResponse.data,
route: thirdResponse.data,
});


}

Have you used axios.all ? You can try with something similar:

axios.all([axios.get(`firstrequest`),
axios.get(`secondrequest`),
axios.get(`thirdrequest`)])
.then(axios.spread((firstResponse, secondResponse, thirdResponse) => {
console.log(firstResponse.data,secondResponse.data, thirdResponse.data);
}))
.catch(error => console.log(error));

This will take all your get and will put it inside a response that has to be called with .data like: firstResponse.data

A little late to the party, but I like this pattern of chaining promises, returning them to keep the promise chain alive.

axios
.get('https://maps.googleapis.com/maps/api/geocode/json?&address=' + this.props.p1)
.then(response => {
this.setState({ p1Location: response.data });
return axios.get('https://maps.googleapis.com/maps/api/geocode/json?&address=' + this.props.p2);
})
.then(response => {
this.setState({ p2Location: response.data });
return axios.get('https://maps.googleapis.com/maps/api/geocode/json?&address=' + this.props.p3);
})
.then(response => {
this.setState({ p3Location: response.data });
}).catch(error => console.log(error.response));

For better performance and cleaner code:

1. Use promise.all() or axios.all() to execute request1 and request2 at the same time. So request2 will execute without waiting for request1 response. After request1 and request2 return the response, request3 will continue execute based on the returned response data as parameter.
2. Template Strings use back-ticks (``)

async componentDidMount(){
try{
const [request1, request2] = await Promise.all([
axios.get(`https://maps.googleapis.com/maps/api/geocode/json?&address=${this.props.p1}`),
axios.get(`https://maps.googleapis.com/maps/api/geocode/json?&address=${this.props.p2}`)
]);


const request3 = await axios.get(`https://maps.googleapis.com/maps/api/directions/json?origin=place_id:${request1.data.results.place_id}&destination=place_id:${request2.data.results.place_id}&key=${API-KEY-HIDDEN}`);
console.log(request3);
}
catch(err){
console.log(err)
}
}

create array of promise and then use reduce.

/**
* Runs promises from array of functions that can return promises
* in chained manner
*
* @param {array} arr - promise arr
* @return {Object} promise object
*/
function runPromiseInSequence(arr, input) {
return arr.reduce(
(promiseChain, currentFunction) => promiseChain.then(currentFunction),
Promise.resolve(input)
)
}


// promise function 1
function p1(a) {
return new Promise((resolve, reject) => {
resolve(a * 5)
})
}


// promise function 2
function p2(a) {
return new Promise((resolve, reject) => {
resolve(a * 2)
})
}


// function 3  - will be wrapped in a resolved promise by .then()
function f3(a) {
return a * 3
}


// promise function 4
function p4(a) {
return new Promise((resolve, reject) => {
resolve(a * 4)
})
}


const promiseArr = [p1, p2, f3, p4]
runPromiseInSequence(promiseArr, 10)
.then(console.log)   // 1200

For simultaneous requests with axios, you can use axios.all() plus axios.spread()

axios.spread() is used to spread the array of arguments into multiple arguments, so that all data can be passed to the function.

Example

const url_1 = '', url_2 = '';


axios.all([
axios.get(url_1),
axios.get(url_2)
])
.then(
axios.spread((resp1, resp2) => {
let id_1 = resp1.data.results.place_id
let id_2 = resp2.data.results.place_id
let url_3 = ''                          // <---- Build your third URL here
axios.get(url_3)
.then((resp3) => {
// You have all the data available here to useState()
})
})
)
.catch((error) => console.log(error))