admin管理员组文章数量:1331940
I'm making an API call in my function to fetch some data. Then I need to make multiple API calls for each item of the data returned, based on a specific value from the first call. I have a problem when I'm rendering state, the values added from multiple promises are not present during rendering.
So far I have something like this:
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}))
Function which returns array of promises looks something like that:
fetchMoreData(arr) {
const promises = arr.map(el => {
return fetch(someUrl)
.then(data => data.json())
.then(data => data)
})
return promises;
}
I think that my chaining the Promise.all inside another promise is not good, is there a remended and more elegant way to do this ?
I'm making an API call in my function to fetch some data. Then I need to make multiple API calls for each item of the data returned, based on a specific value from the first call. I have a problem when I'm rendering state, the values added from multiple promises are not present during rendering.
So far I have something like this:
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}))
Function which returns array of promises looks something like that:
fetchMoreData(arr) {
const promises = arr.map(el => {
return fetch(someUrl)
.then(data => data.json())
.then(data => data)
})
return promises;
}
I think that my chaining the Promise.all inside another promise is not good, is there a remended and more elegant way to do this ?
Share edited Sep 17, 2019 at 17:40 vrt1515 asked Sep 17, 2019 at 17:35 vrt1515vrt1515 1631 gold badge3 silver badges11 bronze badges 2-
1
Returning
Promise.all
result would defer thesetState
correctly. Right now, like Patrick said, it's mutating the array after it made it to the state. – Emile Bergeron Commented Sep 17, 2019 at 17:45 -
2
One minor point:
.then(data => data)
adds nothing at all. – Scott Sauyet Commented Sep 17, 2019 at 18:07
2 Answers
Reset to default 6You are correct in saying that your approach is not good, and here's why:
.then(data => {
const newData = [...data];
Promise.all(fetchMoreData(newData)).then(el => {
// more calculation
newData.push(el.someValue);
})
return newData;
})
return newData
happens before you reach newData.push(el.someValue)
, and since they reference the same array, that means you're calling setState()
and passing an array which is mutated asynchronously, independent of when your ponent re-renders.
Essentially, you have created a race-condition that will make your ponent state indeterminate because it is based on whether the fetch()
operations plete before or after the React framework decides to re-render the ponent.
To solve this, there are two options that e to mind, so choose whichever is more readable to you, or consistent with your coding style, but first let's address a small refactor to make your helper function more canonical.
An asynchronous function should prefer to return a promise to an array, not an array of promises:
fetchMoreData(arr) {
const promises = arr.map(el =>
fetch(someUrl)
.then(res => res.json())
);
return Promise.all(promises);
}
With that in mind, let's continue to the two solutions:
Nest (and return) the promise chain dependent on the previous scope
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 });
});
Notice that we return fetchMoreData(array).then(...)
, and within the nested continuation, also return array
. This will allow array
to be passed along to data
in the next chain.
Pass the dependency from the previous scope through a flattened promise chain
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 });
});
Here, we encapsulate the dependency in another Promise.all()
and pass both the array
and promise
along to the next flattened chain, which we then use array destructuring syntax to separate out again in the callback parameter. From there, we perform the additional calculations and then pass the array along to the final chain.
I think I'm probably missing something you're trying to do. If you simply need to have access to the the individual items when their "more data" Promises resolve, it is easily captured in the closure of that function.
const fetchMoreData = (thingy) =>
fetch (`https://example./api/thingy/${thingy.id}`)
.then (res => res .json ())
.then (res => ({...thingy, ...res})) // NOTE: both `thingy` and `res` are in scope
fetch('https://example./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>
Are you looking to do something more plex that this pattern won't allow?
Update
Based on the ments, this is a version that uses a public REST API and avoids my fetch
override:
const fetchMoreData = (overview) =>
fetch (`https://jsonplaceholder.typicode./todos/${overview.id}`)
.then (res => res .json () )
.then (details => ({overview, details})) // `overview` and `details` both in scope
fetch('https://jsonplaceholder.typicode./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}`) )
Note that this API doesn't include only overview material in its group listings, so in fact overview
and details
contain the same information. But yours can include whatever you like.
本文标签: javascriptMultiple API calls with fetch in chain and PromiseallStack Overflow
版权声明:本文标题:javascript - Multiple API calls with fetch in chain and Promise.all - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1742262503a2442777.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论