在下面的代码中,我的
console.log(response)
上有一个空数组,但 console.log(filterdIds)
函数内的 getIds
显示了我想要的数据。我觉得我的决心不对。
请注意,我运行
do..while
一次进行测试。 API 是分页的。如果记录是昨天的,它将继续进行,如果不是,则do..while
将停止。
有人能指出我正确的方向吗?
const axios = require("axios");
function getToken() {
// Get the token
}
function getIds(jwt) {
return new Promise((resolve) => {
let pageNumber = 1;
const filterdIds = [];
const config = {
//Config stuff
};
do {
axios(config)
.then((response) => {
response.forEach(element => {
//Some logic, if true then:
filterdIds.push(element.id);
console.log(filterdIds);
});
})
.catch(error => {
console.log(error);
});
} while (pageNumber != 1)
resolve(filterdIds);
});
}
getToken()
.then(token => {
return token;
})
.then(jwt => {
return getIds(jwt);
})
.then(response => {
console.log(response);
})
.catch(error => {
console.log(error);
});
我也不确定在
getIds
函数中将拒绝放在哪里,因为 do..while
。
根本问题是
resolve(filterdIds);
在请求触发之前同步运行,因此保证为空。
如果您预先知道需要多少页(或者如果您使用块大小来发出多个请求 - 稍后会详细介绍),则 Promise.all
或 Promise.allSettled
可以提供帮助。这些方法并行运行。这是一个可运行的概念验证示例:
const pages = 10; // some page value you're using to run your loop
axios
.get("https://httpbin.org") // some initial request like getToken
.then(response => // response has the token, ignored for simplicity
Promise.all(
Array(pages).fill().map((_, i) => // make an array of request promisess
axios.get(`https://jsonplaceholder.typicode.com/comments?postId=${i + 1}`)
)
)
)
.then(responses => {
// perform your filter/reduce on the response data
const results = responses.flatMap(response =>
response.data
.filter(e => e.id % 2 === 0) // some silly filter
.map(({id, name}) => ({id, name}))
);
// use the results
console.log(results);
})
.catch(err => console.error(err))
;
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
网络选项卡显示并行发生的请求:
如果页面数未知,并且您打算一次触发一个请求,直到您的 API 通知您页面结束,则顺序循环很慢,但可以使用。对于此策略,异步/等待更清晰:
(async () => {
// like getToken; should handle err
const tokenStub = await axios.get("https://httpbin.org");
const results = [];
// page += 10 to make the snippet run faster; you'd probably use page++
for (let page = 1;; page += 10) {
try {
const url = `https://jsonplaceholder.typicode.com/comments?postId=${page}`;
const response = await axios.get(url);
// check whatever condition your API sends to tell you no more pages
if (response.data.length === 0) {
break;
}
for (const comment of response.data) {
if (comment.id % 2 === 0) { // some silly filter
const {name, id} = comment;
results.push({name, id});
}
}
}
catch (err) { // hit the end of the pages or some other error
break;
}
}
// use the results
console.log(results);
})();
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
这是顺序请求瀑布:
如果您想提高并行度,可以使用任务队列或分块循环。 p-limit 是一个有用的库,用于限制您的
Promise.all
工作。分块循环将结合这两种技术来一次请求 n
记录并检查块中的每个结果的终止条件。这是一个简单的示例,它去掉了过滤操作,这是异步请求问题的附带条件,可以在响应到达后同步完成:
(async () => {
const results = [];
const chunk = 5;
for (let page = 1;; page += chunk) {
try {
const responses = await Promise.all(
Array(chunk).fill().map((_, i) =>
axios.get(`https://jsonplaceholder.typicode.com/comments?postId=${page + i}`)
)
);
for (const response of responses) {
for (const comment of response.data) {
const {name, id} = comment;
results.push({name, id});
}
}
// check end condition
if (responses.some(e => e.data.length === 0)) {
break;
}
}
catch (err) {
break;
}
}
// use the results
console.log(results);
})();
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
(上图是 100 个请求的例外情况,但一次性 5 的块大小是可见的)
请注意,这些片段是概念验证,可以减少不加区分地捕获错误,确保捕获所有抛出异常等。将其分解为子函数时,请确保
.then
和 await
所有承诺在调用者中——不要尝试将其转换为同步代码。
另请参阅
new Promise
来帮助解析已返回 Promise 的代码。要退一步思考为什么会遇到这个问题,我们必须考虑同步和异步 JavaScript 代码如何协同工作。你的 同步
getIds
函数将运行至完成,单步执行每一行,直到结束。
axios
函数调用返回一个Promise
,它是一个代表未来实现或拒绝值的对象。直到事件循环的下一个周期(最早),Promise
才会解决,并且您的代码告诉它在返回挂起值时执行一些操作(这是 中的回调) .then()
方法)。但是你的主 getIds
函数不会等待...它会调用 axios 函数,为返回的 Promise 提供将来要做的事情,然后
继续前进,越过 do/while 循环并转到解析方法,该方法从您在函数开头创建的 Promise 返回一个值...但是
axios
Promise 尚未解析,因此 filterIds
尚未填充。当您将要创建的 Promise 的 resolve
方法移动到 axios 解析的 Promise 将调用的回调中时,它开始工作
因为现在您的 Promise 在解析自身之前等待 axios 解析。 希望这能让您了解如何实现多页目标。
我忍不住想到有一种更干净的方法可以让您一次获取多个页面,然后如果最后一页表明还有其他页面需要获取,则递归地继续获取。您可能仍然需要添加一些额外的逻辑来过滤掉您批量获取的任何不符合您正在寻找的条件的页面,但这应该可以帮助您完成大部分工作:
async function getIds(startingPage, pages) {
const pagePromises = Array(pages).fill(null).map((_, index) => {
const page = startingPage + index;
// set the page however you do it with axios query params
config.page = page;
return axios(config);
});
// get the last page you attempted, and if it doesn't meet whatever
// criteria you have to finish the query, submit another batch query
const lastPage = await pagePromises[pagePromises.length - 1];
// the result from getIds is an array of ids, so we recursively get the rest of the pages here
// and have a single level array of ids (or an empty array if there were no more pages to fetch)
const additionalIds = !lastPage.done ? [] : await getIds(startingPage + pages, pages);
// now we wait for all page queries to resolve and extract the ids
const resolvedPages = await Promise.all(pagePromises);
const resolvedIds = [].concat(...resolvedPages).map(elem => elem.id);
// and finally merge the ids fetched in this methods invocation, with any fetched recursively
return [...resolvedIds, ...additionalIds];
}