86

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?

Freddy
  • 1,229
  • 2
  • 13
  • 22

8 Answers8

105

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,
  });

}
Matt Aft
  • 8,742
  • 3
  • 24
  • 37
  • 2
    Perfect. This is great. – Freddy May 26 '17 at 11:01
  • 4
    Incase of error in first request (which fetches firstResponse only), How does this pattern handle that scenario? – Dravidian Apr 08 '20 at 19:29
  • 5
    @Dravidian you can wrap in a try/catch or chain a .catch on the promise.all. If you want to handle if one fails while other passes you can also swap to use `allSettled` instead of `all`. and you also probably want more logic around `firstResponse.data.results.place_id` value actually existing before using in 3rd request. but the general idea is there – Matt Aft Apr 08 '20 at 20:19
  • how to access state inside the Promise.all()? – justin Apr 11 '20 at 05:04
59

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));
dotNET
  • 33,414
  • 24
  • 162
  • 251
chrisz
  • 1,371
  • 13
  • 8
  • Using this technique how could you go about catching each individual error? Similar question here: https://github.com/axios/axios/issues/708#issuecomment-317224152 – John Oct 14 '21 at 22:28
  • Yes - this is what I was looking for: how to sequentially chain Axios calls (so not with Promise.all(), to run them in parallel - which is rarely what you need) ... – leo Jul 27 '22 at 18:22
35

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

Daniel Puiu
  • 962
  • 6
  • 21
  • 29
Ricardo Gonzalez
  • 1,827
  • 1
  • 14
  • 25
  • 6
    What if the input to the 'secondrequest' comes from the response of the 'firstrequest'? – mojave Sep 27 '18 at 23:32
  • well in this case i would go with something like this: ``` axios.get('firstrequest). then( firstResponse => { axios.get('secondResponse', firstResponse.data.id) } ) ``` – Ricardo Gonzalez Sep 28 '18 at 15:39
  • how to pass `Auth` in headers? – Zain Khan Dec 06 '19 at 13:01
  • 3
    Remember this is deprecated and axios official documentation recommends using ```Promise.all``` –  Jan 10 '21 at 15:04
  • @ZaInKhAn `axios.get('request', { headers: { 'X-Octopus-ApiKey': process.env.API_KEY, 'Content-Type': 'application/json' } })` – lornasw93 Sep 27 '21 at 02:59
13

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)
    }
}
Benson Toh
  • 1,970
  • 1
  • 10
  • 13
11

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
       })
Morleee
  • 349
  • 2
  • 8
  • If you comment out the Promise.all code you'll notice that the three API calls are still being made immediately. This can only work by chance, as the Promise.all isn't taking effect – Drenai Sep 11 '20 at 13:31
3

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)) 
Gass
  • 7,536
  • 3
  • 37
  • 41
1

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.

milkersarac
  • 3,399
  • 3
  • 31
  • 31
0

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