简体   繁体   English

如何链接 JavaScript/axios Promises,当前 promise 确定未来是否做出承诺?

[英]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:我如何认为这需要完成:

  1. create an array创建一个数组
  2. make a request to the api向 api 提出请求
  3. push the response to the array将响应推送到数组
  4. make another request to the API but with the query params from the prior call.向 API 发出另一个请求,但使用先前调用中的查询参数。
  5. push the response to the array, repeat step 4 and 5 until API no longer gives a next URL.将响应推送到数组,重复步骤 4 和 5,直到 API 不再给出下一个 URL。
  6. Act on that received data after all the promises are complete.在所有承诺完成后对收到的数据采取行动。 (example can simply be console logging all of that data) (示例可以简单地是控制台记录所有这些数据)

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:我试过的:

  • I've tried inside the first 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 循环不会等到请求返回。
  • I've tried declaring a function which contains my axios request plus a .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 自己调用。
  • Looked into but didn't try promise.all()/axios.all() as that seemed to only work if you knew all of the URLs you were hitting upfront, so it didn't seem to apply to this scenario.调查但没有尝试 promise.all()/axios.all() 因为这似乎只有在您知道所有您正在访问的 URL 的情况下才有效,因此它似乎不适用于这种情况。
  • tried what was suggested in this answer , but seems the.then doesn't occur after all promises have been returned.尝试了此答案中的建议,但似乎在所有承诺都已返回后,.then 并没有发生。

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM