多个API调用,并在chain和Promise.all中进行提取

时间:2019-09-17 17:35:29

标签: javascript reactjs fetch es6-promise

我正在函数中进行API调用以获取一些数据。 然后,我需要根据第一个调用中的特定值,对返回的每个数据项进行多个API调用。我在渲染状态时遇到问题,在渲染期间不存在从多个promise添加的值。

到目前为止,我有这样的事情:

fetch(url)
.then(resp => resp.json())
.then(data => {
  //do some calculations and populate new array and return it
})
.then(data => {
   const newData = [...data];
   Promise.all(functionWhichReturnsArrayOfPromises).then(el => {
      // more calculation
      newData.push(el.someValue);
   })
   return newData;
})
.then(data => this.setState({data: data}))

返回promise数组的函数如下所示:

fetchMoreData(arr) {
   const promises = arr.map(el => {
      return fetch(someUrl)
      .then(data => data.json())
      .then(data => data)
   })
   return promises;
}

我认为将Promise.all链接到另一个promise中是不好的,是否有推荐的更优雅的方法来实现此目的?

2 个答案:

答案 0 :(得分:5)

您正确地说您的方法不好,这就是原因:

.then(data => {
   const newData = [...data];
   Promise.all(fetchMoreData(newData)).then(el => {
      // more calculation
      newData.push(el.someValue);
   })
   return newData;
})

return newData发生在到达newData.push(el.someValue)之前,并且由于它们引用相同的数组,这意味着您正在调用setState()并传递一个异步突变的数组,而与您的时间无关。重新渲染组件。

基本上,您已经创建了一个竞态条件,该竞态条件将使组件状态不确定,因为它基于fetch()操作是在React框架决定重新渲染组件之前还是之后完成的。


要解决此问题,我想到了两个选项,因此请选择对您更易读或与您的编码风格一致的选项,但首先让我们解决一个小的重构问题,以使您的辅助函数更规范。

一个异步函数应该更喜欢将一个promise返回到一个数组,而不是一个promises数组:

fetchMoreData(arr) {
   const promises = arr.map(el =>
     fetch(someUrl)
       .then(res => res.json())
   );

   return Promise.all(promises);
}

考虑到这一点,让我们继续两种解决方案:

根据先前的范围嵌套(并返回)承诺链

fetch(url)
  .then(res => res.json())
  .then(data => {
    // do some calculations and populate new array and return it
  })
  .then(array => {
    // nest the promise chain
    return fetchMoreData(array).then(result => {
      // more calculations dependent on array from previous scope
      result.forEach(el => {
        array.push(el.someValue);
      });

      // pass array along
      return array;
    });
  })
  .then(data => {
    this.setState({ data });
  });

请注意,我们return fetchMoreData(array).then(...)以及嵌套的延续中也return array。这样可以将array传递到下一个链中的data

通过扁平化的诺言链传递先前范围的依赖项

fetch(url)
  .then(res => res.json())
  .then(data => {
    // do some calculations and populate new array and return it
  })
  .then(array => {
    const promise = fetchMoreData(array);
    // pass the dependency along
    return Promise.all([array, promise]);
  })
  // destructure dependencies
  .then(([array, result]) => {
    // more calculations dependent on array from previous scope
    result.forEach(el => {
      array.push(el.someValue);
    });

    // pass array along
    return array;
  })
  .then(data => {
    this.setState({ data });
  });

在这里,我们将依赖项封装在另一个Promise.all()中,并将arraypromise传递到下一个扁平化链,然后使用数组解构语法在其中再次分离回调参数。从那里开始,我们执行其他计算,然后将数组传递到最终链。

答案 1 :(得分:1)

我想我可能缺少您要尝试做的事情。如果您只需要在各个项目的“更多数据”承诺解决时访问这些项目,则可以在该函数的关闭中轻松捕获该项目。

const fetchMoreData = (thingy) => 
  fetch (`https://example.com/api/thingy/${thingy.id}`)
    .then (res => res .json ())
    .then (res => ({...thingy, ...res})) // NOTE: both `thingy` and `res` are in scope

fetch('https://example.com/api/thingy')
  .then (res => res .json ())
  .then (thingies => Promise .all (thingies .map (fetchMoreData)) )
  .then (allThingies => console .log (`Results: ${JSON.stringify(allThingies)}`))
  // .catch ( ... )
<script>
// dummy version of fetch for testing
const fetch = (url, opts) => Promise.resolve({
  json: () => {
    const result = (url .endsWith ('thingy'))
      ? [{id: 1, x: 'a'}, {id: 2, x: 'b'}, {id: 3, x: 'c'}]
      : {more: ['', 'foo', 'bar', 'baz'] [url .slice (url .lastIndexOf ('/') + 1)]} 
    console.log(`fetch('${url}') ~~> ${JSON.stringify(result)}`)
    return result
  }
})</script>

您是否想做一些更复杂的事情,而这种模式是不允许的?

更新

根据评论,此版本使用公共REST API,并避免了fetch的覆盖:

const fetchMoreData = (overview) => 
  fetch (`https://jsonplaceholder.typicode.com/todos/${overview.id}`)
    .then (res => res .json () ) 
    .then (details => ({overview, details})) // `overview` and `details` both in scope

fetch('https://jsonplaceholder.typicode.com/todos')
  .then (res => res .json ()) 
  // `slice` because we don't need dozens for a demo
  .then (overviews => Promise .all (overviews .slice (0, 3) .map (fetchMoreData)) )
  .then (console.log)
  .catch (err => console.log(`error: ${err}`) )

请注意,此API在其组列表中不只包含概述材料,因此实际上overviewdetails包含相同的信息。但是您可以随便添加任何内容。