Axios: 链接多个API请求

86

我需要从Google Maps API中链接几个API请求,并尝试使用Axios完成此操作。

以下是第一个请求,在componentWillMount()中:

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

这是第二个请求:

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

然后我们有第三个请求,这个请求依赖于前两个请求已经完成:

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

我该如何链接这三个调用,以便第三个调用发生在前两个之后?

8个回答

105

首先,不确定你是否想在componentWillMount中执行此操作,最好将其放在componentDidMount中,并设置一些默认状态,等待请求完成后再更新。其次,要限制您编写的setStates数量,因为它们可能会导致额外的重新渲染,以下是使用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,
  });

}

2
完美。这太棒了。 - Freddy
4
如果第一个请求(仅获取firstResponse)出现错误,这种模式如何处理该情况? - Dravidian
5
@Dravidian,你可以使用try/catch来包装代码块,或者在promise.all上链接.catch。如果你想处理其中一个promise失败而其他仍然成功的情况,你也可以使用allSettled代替all。并且在第3个请求中使用firstResponse.data.results.place_id之前,最好添加更多的逻辑确认其实际存在的值。但总体思路已经表达清楚了。 - Matt Aft
如何在Promise.all()内部访问状态? - justin

60

我来晚了,但我喜欢这种使用promise链式调用的模式,可以返回promise以保持promise链的连续性。

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

使用这种技术,你如何捕获每个单独的错误?类似的问题在这里:https://github.com/axios/axios/issues/708#issuecomment-317224152 - John
是的 - 这正是我在寻找的:如何顺序链接 Axios 调用(因此不使用 Promise.all(),以并行方式运行它们 - 这很少是您所需的)。 - leo

35

你用过 axios.all 吗?你可以尝试使用类似的方法:

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

这将获取你所有的数据并将其放入一个响应中,需要使用.data进行调用,例如:firstResponse.data


6
如果“secondrequest”的输入来自“firstrequest”的响应,会怎样? - mojave
在这种情况下,我会选择像这样的代码:axios.get('firstrequest') .then(firstResponse => { axios.get('secondResponse', firstResponse.data.id) }) - Ricardo Gonzalez
如何在头部传递 Auth - Zain Khan
3
记住这已经被弃用,axios官方文档建议使用Promise.all - user10606394
@ZaInKhAn axios.get('请求', { headers: { 'X-Octopus-ApiKey': process.env.API_KEY, 'Content-Type': 'application/json' } }) - lornasw93

13

为了更好的性能和更干净的代码:

1. 使用promise.all()或axios.all()同时执行request1和request2。这样,request2将在不等待request1响应情况下执行。当request1和request2返回响应后,request3将继续根据返回的响应数据作为参数执行。
2. 模板字符串使用反引号(``)

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

1
要在axios.all()的情况下单独捕获错误,请使用这个 - DroidDev
我该如何捕获来自不同请求的响应?顺便说一句,谢谢。 - Paul Iverson Cortez

10

我认为你需要类似这样的东西:

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

1
如果您注释掉 Promise.all 代码,您会注意到三个 API 调用仍然会立即执行。这只能是偶然发生的,因为 Promise.all 没有生效。 - Drenai

3

对于同时发出的请求,您可以使用axios.all()axios.spread()

axios.spread()用于将参数数组展开为多个参数,以便将所有数据传递给函数。

示例

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)) 

1
这与JS的Promises有关。您可以用不同的方式解决它。对我来说最简单的方法是,您应该将每个请求从第一个到第三个嵌套。也就是说,从第一个请求开始,您应该将第二个axios.get(url)放入第一个请求的.then()中,并将第三个请求放入第二个请求的.then()中。
对于一般的promise,您期望在.then()部分内解决promise,并且您可以访问response。因此,通过嵌套,您可以以不太优雅的方式解决异步问题。

1
正如所说,这并不是一个优雅的解决方案。我建议查看其他答案以获得更好的解决方案。 - milkersarac

0
创建 Promise 数组,然后使用 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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接