【问题标题】:jQuery Recursive AJAX Call PromisejQuery 递归 AJAX 调用承诺
【发布时间】:2014-06-14 19:56:05
【问题描述】:

我仍在试图弄清楚如何在递归 AJAX 调用中使用 jQuery 延迟对象。我有这样的代码

function request(page, items){    

    //building the AJAX return value for JSFiddle dummy AJAX endpoint
    var ret = {
        totalPage: 10,
        currentPage: page,
        items: []
    };
    for (var i = page; i < (page + 5); i++){
        ret.items.push(i);
    }

    //calling the AJAX
    $.ajax({
        url: '/echo/json/',
        method: 'POST',
        dataType: 'json',
        data: {
            delay: 1,
            json: JSON.stringify(ret)
        },
        success: function(data){
            if (data.currentPage <= data.totalPage){
                var filtered = data.items.filter(function(el){
                    return el % 2 == 1;
                });
                var newitems = items.concat(filtered);
                console.dir(newitems);
                request(data.currentPage + 1, newitems);
            } else {
                console.dir(items);
                //resolve all item
            }
        }
    });
}

function requestAll(){
    request(1, []);
    //should return a promise tha contains all items
}

这是 JSFiddle http://jsfiddle.net/petrabarus/BHswy/

我知道如何在单个 AJAX 调用中使用 Promise,但我不知道如何在递归 AJAX 调用中使用它。我想以类似于下面的方式调用requestAll 函数

var promise = requestAll();
promise.done(function(items){
    console.dir(items);
});

我该怎么做?

【问题讨论】:

  • 我认为如果你这样做,你仍然可以使用一个承诺:jsfiddle.net/gGAAy
  • 啊,我不知道我能不能做到。谢谢。
  • @JasonP:这并没有真正使用承诺的力量……
  • 仅供参考,您的代码实际上并不是递归的,因为在您从 success 处理程序调用下一个代码之前,request() 已经完成执行。
  • 我认为我仍然可以将其称为递归,因为 AJAX 实际上会调用之前调用它的同一个函数。

标签: javascript jquery promise


【解决方案1】:

如果您想使用 Promise,则不应使用 success 参数。相反,您想return 一个承诺,并且您想使用then 将一个承诺的结果转换成不同的东西,甚至可能是另一个承诺。

function request(page) {    
    …
    // return the AJAX promise
    return $.ajax({
        url: '/echo/json/',
        method: 'POST',
        dataType: 'json',
        data: {
            delay: 1,
            json: JSON.stringify(ret)
        }
    });
}

function requestOddsFrom(page, items) {
    return request(page).then(function(data){
        if (data.currentPage > data.totalPage) {
            return items;
        } else {
            var filtered = data.items.filter(function(el){ return el%2 == 1; });
            return requestOddsFrom(data.currentPage + 1, items.concat(filtered));
        }
    });
}

function requestAll(){
    return requestOddsFrom(1, []);
}

requestAll().then(function(items) {
    console.dir(items);
});

【讨论】:

  • 其实这很简单,加上 OP 已经拥有的尾递归,它比我最初想象的还要简单。这里的key to promises 是被称为requestOddsFrom(…) 的“递归”将被return 采用为then,因此使整体结果成为内部承诺的结果。
  • 只有了解当从.then() 处理程序返回承诺时,承诺如何链接的人才能简单明了。无论如何,这不是一个简单的概念(许多人在使用 Promise 时并不了解该级别的使用),并且不容易理解代码逻辑的工作方式或 Promise 的流程。一旦你完全理解它,也许它是微不足道的,但请对没有你那么先进的人有一些同情。你对它的简单解释并不能帮助我理解它是如何工作的,而这正是我希望你能进一步解释的。
  • 这就是我不遵循的。 requestOddsFrom() 返回调用 request().then() 的值。这将是.then() 返回的第二代承诺。我明白了。我不明白的是,N 次对requestOddsFrom() 的调用如何将数据返回到requestAll() 中该承诺的.done() 处理程序。我尝试查看 jQuery .then() 代码,看看我是否可以遵循它,但这是我见过的一些最迟钝的代码(我认为是为了让它变小),我无法遵循它的逻辑。 jQuery 文档对这个话题也没有帮助。
  • @jfriend00:也许你不明白“第二代承诺”是什么意思。这是一个承诺,将使用从then 回调返回的requestOddsFrom(…) 递归调用承诺的结果来解决。或者它只会使用从then 回调返回的items 来解决,以防没有更多页面。
  • 是的,我不明白对 requestOddsFrom() 的 N 次递归调用如何将数据返回到从对 requestOddsFrom() 的原始调用返回的第一个承诺,因为这是 requestAll().done() 的承诺正在使用。我不明白这是怎么回事。
【解决方案2】:

由于您已经一个接一个地对 Ajax 操作进行排序,而无需完全重构您的代码,您可以只使用在最后一次 Ajax 调用时解决的延迟:

function request(page, items, defer){    

    //building the AJAX return value for JSFiddle dummy AJAX endpoint
    var ret = {
        totalPage: 10,
        currentPage: page,
        items: []
    };
    for (var i = page; i < (page + 5); i++){
        ret.items.push(i);
    }

    //calling the AJAX
    $.ajax({
        url: '/echo/json/',
        method: 'POST',
        dataType: 'json',
        data: {
            delay: 1,
            json: JSON.stringify(ret)
        },
        success: function(data){
            if (data.currentPage <= data.totalPage){
                var filtered = data.items.filter(function(el){
                    return el % 2 == 1;
                });
                var newitems = items.concat(filtered);
                console.dir(newitems);
                request(data.currentPage + 1, newitems, defer);
            } else {
                console.dir(items);
                //resolve the deferred
                defer.resolve(items);
            }
        }
    });
}

function requestAll(){
    var deferred = jQuery.Deferred();
    request(1, [], deferred);
    return deferred.promise();
}

requestAll().done(function(items) {
    // all ajax calls are done
});

好的,在学习了很多新的 Promise 之后,这是一个使用 Promise 链接的完整 Promise 版本(从 .then() 处理程序返回一个 Promise)。从 Benji 的实现中借用和学习的概念,但组织方式略有不同,并为学习添加了注释(如果没有 cmets 和虚拟 Ajax 调用的东西,它实际上会很短):

function requestPages(startPage, endPage) {

    function request(page, items){    
        // building the AJAX return value for 
        // JSFiddle dummy AJAX endpoint
        var ret = {
            currentPage: page,
            items: []
        };
        for (var i = page; i < (page + 5); i++){
            ret.items.push(i);
        }

        // Do Ajax call, return its promise
        return $.ajax({
            url: '/echo/json/',
            method: 'POST',
            dataType: 'json',
            data: {
                delay: 1,
                json: JSON.stringify(ret)
            }
        }).then(function(data) {
            // mock filter here to give us just odd values
            var filtered = data.items.filter(function(el){
                return el % 2 == 1;
            });
            // add these items to the ones we have so far
            items = items.concat(filtered);

            // if we have more pages to go, then do the next one
            if (page < endPage){
                // Advance the currentPage, call function to process it and
                // return a new promise that will be chained back to the 
                // promise that was originally returned by requestPages()
                return request(page + 1, items);
            } else {
                // Finish our iteration and 
                // return the accumulated items.
                // This will propagate back through 
                // all the other promises to the original promise
                // that requestPages() returned
                return(items);
            }
        });
    }    

    // call the first request and return it's promise    
    return request(startPage, []);
}

// request pages 1 through 10 inclusive
requestPages(1, 10).done(function(items) {
    // all ajax calls are done
    console.log(items);
});

正在工作的 jsFiddle:http://jsfiddle.net/jfriend00/pr5z9/(请耐心等待,执行 10 个 Ajax 调用需要 10 秒,每个调用需要 1 秒)。

我注意到这个版本的一个问题是,因为它只使用$.ajax() 创建的promise,所以代码不能执行.notify() 来触发进度通知。我发现我想在每个 Ajax 调用完成时触发最初返回的 Promise 的进度通知,但是如果没有创建我自己的 Deferred,我不能这样做,因为你不能在 Promise 上执行 .notify(),只能在a 延期。我不知道如何解决这个问题并保持 Benji 不创建/解决您自己的 deferred 的架构。

【讨论】:

  • 为什么投反对票?这正是 OP 所要求的。它还对 OP 的现有代码进行了最少的更改。
  • 它的可恢复性较差,不能正确使用 Promise,并且比 Bergi 的解决方案更长。此外,递归的功能要长得多。 Promise 已经有开箱即用的延续,这种没有返回值的回调递归在这里是不必要的。此外,如果使用真正的 Promise 库,它会提供更糟糕的堆栈跟踪。见github.com/petkaantonov/bluebird/wiki/…
  • @BenjaminGruenbaum - 它如何不正确使用承诺? Bergi 的解决方案方便地省略了一堆 OP 所需的代码(替换为 ...),所以我不知道长度。另外,为什么要否决正确的解决方案。只需点赞另一个你喜欢的人。如果您愿意,您可以拥有自己喜欢的架构,但这可以节省重写整个 OP 的代码,并且它完全符合 OP 的要求。通常不值得投反对票。这里也没有实际的递归,所以我不知道你在说什么关于堆栈跟踪。
  • 投反对票不是为了正确性,而是为了实用性——这是我投票的方式,也是我投票的唯一方式。鉴于 Bergi 现有的解决方案,我发现您的解决方案没有用,并且它鼓励承诺用户社区试图消除的反模式(例如,您的代码不是安全的(大问题),它提供的调试信息更少,它没有使用除了返回值之外的承诺,而且不太清楚)。很抱歉您不欣赏我的投票,但鉴于我过去一直对您的有用答案投赞成票,我认为您会做到这一点。
  • 我更喜欢 Bergi 的代码。它看起来更干净,更模块化,因为我可以轻松修改过滤功能。而将一个承诺的结果转化为另一个承诺的想法正是我的想法。我的问题有点假装,因为那一刻我不清楚我想问什么。删除的代码实际上只是 JSFiddle 的一个虚拟 AJAX 端点(因此可以删除它)。
猜你喜欢
  • 1970-01-01
  • 2021-11-21
  • 2014-02-04
  • 1970-01-01
  • 2018-07-30
  • 2018-05-29
  • 1970-01-01
  • 2019-03-10
  • 1970-01-01
相关资源
最近更新 更多