如何链接 JavaScript/axios Promises,当前的 promise 决定是否做出未来的 promises?

How to chain JavaScript/axios Promises, where the current promise determines if future promises are made?

我正在做一个 node.js 项目,我需要使用 axios 来命中 API。 API returns 与数据一起,一个不同的 URL (URL 中的查询参数发生变化),我需要为每个后续调用命中,以对数据进行分页基本上。该查询参数值不可预测(未编号为“1 2 3 4”)。

我无法一次获得所有 URL,每次请求都必须获得下一个。

我需要所有 api 调用的所有数据。

我认为需要完成的方式:

  1. 创建数组
  2. 向 api
  3. 提出请求
  4. 将响应推送到数组
  5. 向 API 发出另一个请求,但使用先前调用的查询参数。
  6. 将响应推送到数组,重复第4步和第5步,直到API不再给出下一个URL。
  7. 在完成所有承诺后,对收到的数据采取行动。 (示例可以简单地是控制台记录所有这些数据)

我想因为我需要链接所有这些请求,所以我并没有真正获得 promises/async 的好处。所以也许 axios 和 promises 是这项工作的错误工具?

我尝试过的:

该递归函数方法的示例:

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);

我知道最后的控制台日志不起作用,因为它发生在承诺完成之前...所以我必须弄清楚。看来我的承诺循环在第一个承诺之后就失败了。

有时我仍然对承诺的想法感到困惑,我很感激人们愿意分享的任何智慧。

我想因为我需要链接所有这些请求,所以我并没有真正获得 promises/async 的好处。那么也许 axios 和 promises 是这项工作的错误工具?如果是这种情况,请随时提出来。

让我们试试这个。

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);
});

最终,在一天结束时,我确定 axios 只是不适合这项工作的工具。

如果其他人遇到这种情况,也许只是不要为此使用 axios。

我转而使用 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);

感谢为提供帮助而付出的努力。

您的原始代码的问题是您没有等待 axios 调用或对 requestResultPage 的递归调用,因此它在序列完成之前退出 requestResultPage。如果可能的话,我更愿意避免递归调用(有时它们是一个不错的选择),但是为了回答您关于为什么这种方法失败的问题,我将继续使用递归方法。您的 while 循环解决方案是更好的方法。

请注意,您的原始 requestResultPage 根本没有 return 任何内容。通常处理异步操作的函数应该 return a Promise 以便任何调用者都可以知道它何时完成或未完成。这可以在您的原始代码中完成,而不会像这样太麻烦:

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);
});

这也可以使用 async/await 来完成,这样会更简洁一些。声明一个函数 async 意味着它 return 的任何事情都作为隐式已解决的 Promise 完成,因此您不需要 return 承诺,只需在调用时使用 await任何返回 Promise 的东西(axiosrequestResultPage 本身)。:

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);
});

在您最初的实施中,问题是您启动了多个 axios 调用但从未等待它们,因此处理在调用完成之前到达了最后一个 console.log。承诺是伟大的,但你确实需要小心确保 none 从裂缝中溜走。每个人都需要 returned 以便它可以在之后进行 .then() 调用,或者使用 await 以便我们在继续之前知道它已解决。

我喜欢您使用 morePages 并循环直到没有更多的解决方案。这避免了我认为更好的递归。递归有炸毁堆栈的风险,因为整个变量链都保存在内存中,直到所有调用完成,这是不必要的。

请注意,通过使用 await,您需要使您正在执行的功能成为 async,这意味着它现在 return 是 Promise,所以即使你在最后执行 console.log(combinedResults),如果你想 return 调用者的值,他们也需要 await 你的函数来知道它已经完成(或使用 .then() 或者)。