【问题标题】:Keeping track and progress of various ajax post requests跟踪各种 ajax 发布请求的进度和进度
【发布时间】:2015-01-21 18:24:40
【问题描述】:

我正在使用 jQuery 发出各种 ajax POST 请求。我需要跟踪每个请求的成功或失败,以及整个批次的整体进度,以便我可以使用进度条和有关成功请求的数量以及成功请求的信息来更新 UI失败,不在总数中。

在尝试在我的应用程序中实现该功能之前,我一直在使用 jsfiddle 中的一些代码作为概念证明,但到目前为止没有运气。这就是我所拥有的:

// an alternative to console.log to see the log in the web page
var fnLog = function(message) {
    $('#console').append($("<p>" + message + "</p>"));
};

// keeping track of how many ajax calls have been finished (successfully or not)
var count = 0;

// a dummy ajax call that succeeds by default
var fn = function(shouldFail) {
    return $.get(shouldFail ? '/echo/fail/' : '/echo/json/')
        .done(function() { fnLog("done") })
        .fail(function() { fnLog("FAIL") });
};

// a set of different asynchronous ajax calls
var calls = [fn(),fn(),fn(),fn(true),fn(),fn()];

// an attempt to make a collective promise out of all the calls above
$.when.apply($, calls)
    .done(function() { fnLog("all done") })
    .fail(function() { fnLog("ALL FAIL") })
    .always(function() { fnLog("always") })
    .progress(function(arg) { fnLog("progress" + arg) })
    .then(function() { fnLog("finished") });

这一切都在这个小提琴中:http://jsfiddle.net/mmtbo7v6/1/

我需要的是能够提供一个回调,在所有的 Promise 都解决后(无论成功与否)都应该调用该回调。

当上述所有调用都设置为成功时(通过将true 参数删除到数组中的第四个fn 调用),它可以正常工作。输出打印以下内容:

done
done
done
done
done
done
all done
always
finished

但是,即使单个调用设置为失败(因为它在 jsfiddle 中默认设置),输出如下:

done
FAIL
ALL FAIL
always
done
done
done
done

因此,在解决所有承诺后,不会调用任何集体承诺回调(由$.when 调用生成的回调)。如果单个 ajax 调用失败,则根本不会调用最终的 .then

此外,我希望了解如何跟踪这批 ajax 调用的进度,以更新 UI 中的进度条。

【问题讨论】:

    标签: javascript jquery ajax progress-bar promise


    【解决方案1】:

    嗯...我会不公平的。 jQuery 实际上与进度事件捆绑在一起,但我自己讨厌它们,因为我认为它们不能很好地组合或聚合 - 所以我将展示一个更简单的替代方法来代替我 believe is superior 的进度条。

    先到先得:

    “所有承诺都已解决,但有些可能被拒绝”的问题通常称为“解决”。我提供了一个类似问题的答案 here 只给出了结果,here 提供了一个实现,让您可以访问所有结果,甚至是被拒绝的结果。

     function settle(promises){
         var d = $.Deferred();
         var counter = 0;
         var results = Array(promises.length);
         promises.forEach(function(p,i){ 
             p.then(function(v){ // add as fulfilled
                  results[i] = {state:"fulfilled", promise : p, value: v};
             }).catch(function(r){ // add as rejected
                  results[i] = {state:"rejected", promise : p, reason: r};
             }).always(function(){  // when any promises resolved or failed
                 counter++; // notify the counter
                 if (counter === promises.length) {
                    d.resolve(results); // resolve the deferred.
                 }
             });
         });
         return d.promise();
     }
    

    您可以使用settle 代替$.when 来获得您想要的结果。

    至于进度 - 我个人建议将进度回调传递给方法本身。模式是这样的:

    function settle(promises, progress){
         progress = progress || function(){}; // in case omitted
         var d = $.Deferred();
         var counter = 0;
         var results = Array(promises.length);
         promises.forEach(function(p,i){ 
             p.then(function(v){ // add as fulfilled 
                  results[i] = {state:"fulfilled", promise : p, value: v};
             }).catch(function(r){ // add as rejected
                  results[i] = {state:"rejected", promise : p, reason: r};
             }).always(function(){  // when any promises resolved or failed
                 counter++; // notify the counter
                 progress((promises.length - counter) / promises.length);
                 if (counter === promises.length) {
                    d.resolve(results); // resolve the deferred.
                 }
             });
         });
         return d.promise();
     }
    

    这会让你做这样的事情:

    settle([url1, url2, ... url100].map($.get), function(soFar){
        $("#myProgressBar").css("width", (soFar * 100)+"%");
    }).then(function(results){
        console.log("All settled", results);
    ]);
    

    【讨论】:

    • 非常感谢@Benjamin。我已经怀疑 Promise 工作的固有方式不能满足我的目的,但我还没有找到解决它的方法。您的回答不仅解决了我的问题,而且让我更深入地了解如何将 jQuery 的 $.Deferred 用于我自己的目的,并实现我在这个特殊情况下需要的自定义行为。再次感谢。
    • 嘿@Benjamin,我一直在实现这部分的进度部分,并且我对 jQuery 的执行方式进行了更多研究,您说您认为这种方式较差。我很好奇为什么,因为集成到上面显示的代码中实际上非常简单,无需将第二个参数传递给settle 函数。然后在always 回调中增加counter 后,您可以调用d.notify(counter, promises.length) 而不是您自己的progress 回调。最后,您可以将进度回调注册到返回的 Promise 中,每当调用 d.notify 时都会调用它们。
    【解决方案2】:

    事实证明,对于这个问题,有一个更好的替代方案,它掩盖了 Promise 方法。看看两种模式的组合:Observables + Iterables = Reactive Programming。

    反应式编程是使用异步数据流进行编程,即将异步数据流视为可以作为传统集合数据类型进行遍历和转换的集合。 This article 是一个很好的介绍。

    我不会将这个答案帖子转换为教程,所以让我们直接进入解决方案,如下所示。我将使用 RxJS 库,但还有其他用于 JS 响应式编程的库(bacon.js 似乎也很受欢迎)。

    function settle(promises) {
      return Rx.Observable.from(promises).concatMap(function(promise, index) {
        return Rx.Observable.fromPromise(promise).
          map(function(response) {
            return { count: index+1, total: promises.length, state: "fulfilled", promise: promise, value: response };
          }).
          catch(function(reason) {
            return Rx.Observable.of({ count: index+1, total: promises.length, state: "rejected", promise: promise, reason: reason });
          });
      });
    }
    

    函数本身返回一个 observable,它是一个事件流。即,每个承诺的事件都完成了,无论成功与否。我们可以使用返回的 observable 来监听这个流(或者订阅它,如果我们要遵守 RxJS 术语的话)。

    var results = settle(promises);
    
    results.subscribeOnNext(function(results) {
      // process each result as it arrives
      // progress info can be extracted from results.count and results.total
    });
    
    results.subscribeOnCompleted(function() {
      // completion callback
    });
    

    就是这样。更简洁的代码,更实用的编程方法。无需保持状态,一切都以更具声明性的方式表达。只是我们想要做什么不是应该怎么做。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-12-09
      • 1970-01-01
      • 2013-04-20
      • 1970-01-01
      相关资源
      最近更新 更多