多次 API 调用链中提取和 Promise.all
Multiple API calls with fetch in chain and Promise.all
我正在我的函数中进行 API 调用以获取一些数据。
然后我需要根据第一次调用的特定值对返回的每项数据进行多次 API 调用。我在渲染状态时遇到问题,从多个承诺添加的值在渲染期间不存在。
到目前为止我有这样的东西:
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}))
returns promise 数组看起来像这样的函数:
fetchMoreData(arr) {
const promises = arr.map(el => {
return fetch(someUrl)
.then(data => data.json())
.then(data => data)
})
return promises;
}
我认为我将 Promise.all 链接到另一个 promise 中并不好,有推荐的更优雅的方法吗?
你说你的方法不好是对的,原因如下:
.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 框架决定重新渲染组件之前还是之后完成。
为了解决这个问题,我想到了两个选项,所以请选择对您来说更具可读性或与您的编码风格一致的那个,但首先让我们解决一个小的重构问题,使您的辅助函数更规范。
异步函数应该更喜欢return对数组的承诺,而不是承诺的数组:
fetchMoreData(arr) {
const promises = arr.map(el =>
fetch(someUrl)
.then(res => res.json())
);
return Promise.all(promises);
}
考虑到这一点,让我们继续两个解决方案:
嵌套(和return)依赖于先前作用域的承诺链
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()
中,并将array
和promise
都传递给下一个扁平链,然后我们使用数组解构语法将其分离出来再次在回调参数中。从那里,我们执行额外的计算,然后将数组传递到最终链。
我想我可能遗漏了您正在尝试做的事情。如果您只需要在 "more data" Promise 解析时访问单个项目,那么很容易在该函数的闭包中捕获它。
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>
您是否想做一些此模式不允许的更复杂的事情?
更新
根据评论,这是一个使用 public 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 在其组列表中不只包含概述 material,因此实际上 overview
和 details
包含相同的信息。但是你的可以包含任何你喜欢的东西。
我正在我的函数中进行 API 调用以获取一些数据。 然后我需要根据第一次调用的特定值对返回的每项数据进行多次 API 调用。我在渲染状态时遇到问题,从多个承诺添加的值在渲染期间不存在。
到目前为止我有这样的东西:
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}))
returns promise 数组看起来像这样的函数:
fetchMoreData(arr) {
const promises = arr.map(el => {
return fetch(someUrl)
.then(data => data.json())
.then(data => data)
})
return promises;
}
我认为我将 Promise.all 链接到另一个 promise 中并不好,有推荐的更优雅的方法吗?
你说你的方法不好是对的,原因如下:
.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 框架决定重新渲染组件之前还是之后完成。
为了解决这个问题,我想到了两个选项,所以请选择对您来说更具可读性或与您的编码风格一致的那个,但首先让我们解决一个小的重构问题,使您的辅助函数更规范。
异步函数应该更喜欢return对数组的承诺,而不是承诺的数组:
fetchMoreData(arr) {
const promises = arr.map(el =>
fetch(someUrl)
.then(res => res.json())
);
return Promise.all(promises);
}
考虑到这一点,让我们继续两个解决方案:
嵌套(和return)依赖于先前作用域的承诺链
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()
中,并将array
和promise
都传递给下一个扁平链,然后我们使用数组解构语法将其分离出来再次在回调参数中。从那里,我们执行额外的计算,然后将数组传递到最终链。
我想我可能遗漏了您正在尝试做的事情。如果您只需要在 "more data" Promise 解析时访问单个项目,那么很容易在该函数的闭包中捕获它。
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>
您是否想做一些此模式不允许的更复杂的事情?
更新
根据评论,这是一个使用 public 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 在其组列表中不只包含概述 material,因此实际上 overview
和 details
包含相同的信息。但是你的可以包含任何你喜欢的东西。