[英]How to chain JavaScript/axios Promises, where the current promise determines if future promises are made?
I'm working on a node.js project where I need to use axios to hit an API.我正在研究 node.js 项目,我需要使用 axios 来达到 API。 That API returns along with the data, a different URL (a query param changes in the URL) that I need to hit for every subsequent call, to paginate the data basically. API 与数据一起返回,一个不同的 URL (URL 中的查询参数更改),我需要为每个后续调用点击,基本上对数据进行分页。 That query param value is not predictable(it's not numbered "1 2 3 4").该查询参数值是不可预测的(它没有编号为“1 2 3 4”)。
I can't get all of the URLs all at once, I have to get just the next one with each request.我不能一次获取所有的 URL,我必须在每个请求中只获取下一个。
I need all of the data from all of the api calls.我需要来自所有 api 调用的所有数据。
How I'm thinking this needs to be done:我如何认为这需要完成:
I suppose since I'm needing to chain all of these requests I'm not really gaining the benefits of promises/async.我想因为我需要链接所有这些请求,所以我并没有真正获得 Promise/async 的好处。 So maybe axios and promises are the wrong tool for the job?所以也许 axios 和 promises 是错误的工作工具?
What I've tried:我试过的:
axios().then()
a while loop, executing axios requests until there are no more "next" links.我在第一个axios().then()
中尝试了一个 while 循环,执行 axios 请求,直到没有更多的“下一个”链接。 That obviously failed because the while loop doesn't wait until the request is returned.这显然失败了,因为 while 循环不会等到请求返回。.then()
.我尝试声明一个 function ,其中包含我的 axios 请求和一个 .then .then()
。 Inside that .then()
if a next URL was present in the results, I have the function call itself.在 .then .then()
中,如果结果中出现下一个 URL,我有 function 自己调用。Example of that recursive function method:该递归 function 方法的示例:
const apiHeaders={
'Content-Type': 'application/json',
'Authorization': `Bearer ${appToken}`,
'Accept': 'application/json'
};
let initialURL = 'https://example.com/my-api';
let combinedResults =[];
function requestResultPage(pageURL){
let options = {
url:pageURL,
method: 'GET',
headers: apiHeaders,
params:{
limit:1
}
};
axios(options)
.then(function (response) {
// handle success
console.log("data response from API",response.data);
combinedResults.push(response.data.results);
if(typeof response.data.paging.next !== 'undefined'){
console.log("multiple pages detected");
let paginatedURL = response.data.paging.next.link;
console.log("trying:",paginatedURL);
requestResultPage(paginatedURL);
}
}).catch(function(error){
console.log(error);
})
};
requestResultPage(initialURL);
console.log(combinedResults);
I understand that the console log at the end won't work because it happens before the promises finish... So I've gotta figure that out.我知道最后的控制台日志不起作用,因为它发生在承诺完成之前......所以我必须弄清楚这一点。 It seems though my loop of promises fails after the first promise.似乎我的承诺循环在第一个 promise 之后失败了。
Thinking in promises is still confusing to me at times, and I appreciate any wisdom folks are willing to share.有时,在承诺中思考仍然让我感到困惑,我很感激人们愿意分享的任何智慧。
I suppose since I'm needing to chain all of these requests I'm not really gaining the benefits of promises/async.我想因为我需要链接所有这些请求,所以我并没有真正获得 Promise/async 的好处。 So maybe axios and promises are the wrong tool for the job?所以也许 axios 和 promises 是错误的工作工具? If that's the case feel free to call it out.如果是这种情况,请随时大声疾呼。
Let's try this.让我们试试这个。
const apiHeaders={ 'Content-Type': 'application/json', 'Authorization': `Bearer ${appToken}`, 'Accept': 'application/json' }; let initialURL = 'https://example.com/my-api'; let combinedResults =[]; function requestResultPage(pageURL){ let options = { url:pageURL, method: 'GET', headers: apiHeaders, params:{ limit:1 } }; return axios(options).then(function (response) { // handle success console.log("data response from API",response.data); combinedResults.push(response.data.results); if(typeof response.data.paging.next.== 'undefined'){ console;log("multiple pages detected"). let paginatedURL = response.data.paging.next;link. console:log("trying,";paginatedURL); return requestResultPage(paginatedURL). } }).catch(function(error){ console;log(error); }) }. requestResultPage(initialURL).then(() => { console;log(combinedResults); });
Ultimately at the end of the day I determined that axios and promises were simply the wrong tool for the job.最终,在一天结束时,我确定 axios 和 promises 完全是错误的工作工具。
If anyone else runs into this situation, just don't use promises for this.如果其他人遇到这种情况,请不要为此使用承诺。
I switched to using node-fetch and it became very straightforward.我改用 node-fetch ,它变得非常简单。
import fetch from 'node-fetch'; const appToken = "xxxxxxxxxxxxxxxx"; const apiHeaders={ 'Content-Type': 'application/json', 'Authorization': `Bearer ${appToken}`, 'Accept': 'application/json' }; let initialURL = 'https://api.example.com/endpoint'; initialURL = initialURL.concat("?limit=1"); console.log(initialURL); let apiURL = initialURL; let combinedResults =[]; let morePages = true; while(morePages){ console.log("fetching from",apiURL); const response = await fetch(apiURL, { method: 'get', headers: apiHeaders }); let data = await response.json(); console.log(data); combinedResults = combinedResults.concat(data.results); if(typeof(data.paging).== 'undefined' && typeof(data.paging.next).== 'undefined'){ console.log("Another page found.") apiURL = data.paging;next.link, } else{ console.log("No further pages; stopping;"). morePages = false; } } console.log(combinedResults);
Appreciate the effort all invested in trying to help.感谢所有人为提供帮助所付出的努力。
The problem with your original code is that you don't wait on the axios call or the recursive calls to requestResultPage
, so it exits requestResultPage
before the sequence is complete.原始代码的问题在于您不等待 axios 调用或对requestResultPage
的递归调用,因此它在序列完成之前退出requestResultPage
。 I prefer to avoid recursive calls if possible (sometimes they are a good choice), but for the sake of answering your question about why this approach failed, I'll continue with a recursive approach.如果可能的话,我更喜欢避免递归调用(有时它们是一个不错的选择),但是为了回答你关于为什么这种方法失败的问题,我将继续使用递归方法。 Your while
loop solution is a better approach.您的while
循环解决方案是一种更好的方法。
Note that your original requestResultPage
doesn't return anything at all.请注意,您的原始requestResultPage
根本不返回任何内容。 Normally a function that is dealing with async operations should return a Promise
so that any callers can know when it's done or not.通常,处理异步操作的 function 应返回Promise
以便任何调用者都可以知道它何时完成。 This can be done in your original code without too much trouble like this:这可以在您的原始代码中完成,而不会像这样麻烦:
function requestResultPage(pageURL){
let options = {
url:pageURL,
method: 'GET',
headers: apiHeaders,
params:{ limit: 1 }
};
return axios(options) // <---- return the Promise here
.then(function (response) {
console.log("data response from API",response.data);
combinedResults.push(response.data.results);
if(typeof response.data.paging.next !== 'undefined'){
console.log("multiple pages detected");
let paginatedURL = response.data.paging.next.link;
console.log("trying:",paginatedURL);
return requestResultPage(paginatedURL); // <---- and again here
}
})
};
requestResultPage(initialURL).then(() => {
// now that all promises have resolved we have the full set of results
console.log(combinedResults);
}).catch(function(error){ // <--- move the catch out here
console.log(error);
});
This can also be done using async
/ await
which makes it a bit cleaner.这也可以使用async
/ await
来完成,这使它更干净一些。 Declaring a function async
means anything it returns is done as a resolved Promise implicitly, so you don't need to return the promises, just use await
as you call anything that gives a Promise
back ( axios
and requestResultPage
itself).: Declaring a function async
means anything it returns is done as a resolved Promise implicitly, so you don't need to return the promises, just use await
as you call anything that gives a Promise
back ( axios
and requestResultPage
itself).:
async function requestResultPage(pageURL){ // <--- declare it async
let options = {
url:pageURL,
method: 'GET',
headers: apiHeaders,
params:{ limit: 1 }
};
const response = await axios(options) // <---- await the response
console.log("data response from API",response.data);
combinedResults.push(response.data.results);
if(typeof response.data.paging.next !== 'undefined'){
console.log("multiple pages detected");
let paginatedURL = response.data.paging.next.link;
console.log("trying:",paginatedURL);
await requestResultPage(paginatedURL); // <---- and again here
}
// There's no return statement needed. It will return a resolved `Promise`
// with the value of `undefined` automatically
};
requestResultPage(initialURL).then(() => {
// now that all promises have resolved we have the full set of results
console.log(combinedResults);
}).catch(function(error){
console.log(error);
});
In your original implementation the problem was that you kicked off multiple axios
calls but never waited for them, so the processing got to the final console.log
before the calls completed.在您的原始实现中,问题是您启动了多个axios
调用但从未等待它们,因此在调用完成之前处理到达最终的console.log
。 Promises are great but you do need to take care to make sure none slip through the cracks.承诺很棒,但您确实需要注意确保没有人从裂缝中溜走。 Each one needs to either be returned so that it can have a .then()
call after, or use await
so we know it's resolved before continuing.每个都需要被返回,以便它可以在之后进行.then()
调用,或者使用await
以便我们知道它在继续之前已解决。
I like your solution using morePages
and looping until there are no more.我喜欢您使用morePages
和循环直到没有更多的解决方案。 This avoids recursion which is better in my opinion.这避免了递归,这在我看来更好。 Recursion runs the risk of blowing the stack since the entire chain of variables are kept in memory until all calls complete which is unnecessary.由于整个变量链都保存在 memory 中,直到所有调用都完成,递归才会冒着破坏堆栈的风险,这是不必要的。
Note that by using await
you need to make the function you're doing this in be async
, which means it now returns a Promise
, so even though you do the console.log(combinedResults)
at the end, if you want to return the value to the caller, they'll also need to await
your function to know it's done (or use .then()
alternatively).请注意,通过使用await
您需要将 function 设置为async
,这意味着它现在返回Promise
,因此即使您最后执行了console.log(combinedResults)
,如果您想返回对调用者的价值,他们还需要await
你的 function 知道它已经完成(或者使用.then()
)。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.