【问题标题】:How to chain JavaScript/axios Promises, where the current promise determines if future promises are made?如何链接 JavaScript/axios Promises,当前的 Promise 决定是否做出未来的 Promise?
【发布时间】:2022-01-01 05:51:59
【问题描述】:

我正在开发一个 node.js 项目,我需要使用 axios 来访问 API。该 API 与数据一起返回,一个不同的 URL(URL 中的查询参数更改),我需要为每个后续调用点击,基本上对数据进行分页。该查询参数值是不可预测的(它没有编号为“1 2 3 4”)。

我无法一次获取所有 URL,我必须在每个请求中只获取下一个。

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

我认为这需要怎么做:

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

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

我尝试过的:

  • 我已经尝试在第一个axios().then() while 循环中执行 axios 请求,直到没有更多的“下一个”链接。这显然失败了,因为 while 循环不会等到请求返回。
  • 我尝试声明一个函数,其中包含我的 axios 请求和 .then()。在 .then() 中,如果结果中存在下一个 URL,我有函数调用本身。
  • 查看但未尝试 promise.all()/axios.all(),因为这似乎只有在您知道所有您预先访问的 URL 时才有效,因此它似乎不适用于这种情况.
  • 尝试了in this answer 的建议,但似乎 .then 在所有承诺都返回后不会出现。

递归函数方法示例:

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

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

在承诺中思考有时仍然让我感到困惑,我很感激人们愿意分享的任何智慧。

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

【问题讨论】:

  • 你的意思是什么“似乎我的promise循环在第一个promise之后失败了。”,什么表明它失败了?你是说你只看到"data response from API" 登录一次?
  • @NickParsons 是正确的,以及“检测到多个页面”,但之后什么都没有。

标签: javascript node.js promise axios


【解决方案1】:

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

请注意,您原来的 requestResultPage 根本不会返回任何内容。通常处理异步操作的函数应该返回一个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 意味着它返回的任何内容都作为已解决的 Promise 隐式完成,因此您不需要返回承诺,只需使用 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。承诺很棒,但您确实需要注意确保没有人从裂缝中溜走。每个都需要返回,以便之后可以调用.then(),或者使用await,以便我们知道它在继续之前已解决。

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

请注意,通过使用await,您需要将您正在执行此操作的函数设为async,这意味着它现在返回一个Promise,所以即使您在最后执行console.log(combinedResults),如果你想将值返回给调用者,他们还需要await 你的函数知道它已经完成(或者使用.then())。

【讨论】:

    【解决方案2】:

    让我们试试这个。

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

    【讨论】:

    • 欣赏它,这对于函数调用之后的 .then 并返回函数调用是有意义的。不幸的是,尽管这似乎并没有等到所有的承诺都得到回报。我得到的是console.log(combinedResults); 调用后发生的 API 错误。我可以解决 API 错误。在执行 .then() 之前,它似乎并没有真正等待后续调用的结果,这是有道理的,因为返回表示它“完成”。
    【解决方案3】:

    最终,我确定 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);

    感谢大家为提供帮助所付出的努力。

    【讨论】:

    • 嗯?你仍在使用 Promise。
    • 这种方法仍然可以用 axios 完成。
    • @AlwaysLearning 我无法弄清楚,但我在这里使用的解决方案第一次尝试。我曾尝试写同样的东西,但使用 axios,也没有工作。 ?
    • 这有时发生在我身上。有两件事让我感到困惑,但我的代码中只有一件事被破坏了。我“修复”了这两件事,代码工作,后来意识到我的一个修复不是修复,它只是一个不必要的更改。通常,为了学习,我会回去尝试让我的代码只修复损坏的东西。
    猜你喜欢
    • 2017-10-16
    • 1970-01-01
    • 2019-01-14
    • 2016-12-08
    • 2020-03-14
    • 2015-03-21
    • 2018-04-13
    • 2015-10-06
    • 1970-01-01
    相关资源
    最近更新 更多