如何使用自己的回调传递给延迟数组

How to pass to when array of deferred with their own callbacks

是否可以通过自己的回调传递给 $.when 延迟数组?所以我希望在所有延迟都被解析之后调用每个回调,以便将它们传递给 $.when.

例如,在下面的示例中,在解决适当的延迟后立即调用回调,并且在完成所有延迟后仅调用 $.when 中的一般回调

var d1 = $.Deferred();
var d2 = $.Deferred();

d1.done(function(result){alert('d1 is done');});
d2.done(function(result){alert('d2 is done');});

$.when(d1, d2).done(function(result){
alert('here only general callback is called');
});

d1.resolve();

setTimeout(function(){d2.resolve();}, 3000);

编辑: 我需要在哪里使用它。假设我有几个功能:

function SomeFunction1(){
var d = $.Deferred();

//if I define callback here it will call immediately after resolve
//d.done(function(result){
//// do something with result
//});

//$.ajax() or $.get() or $('#someID').load() or whatever else or just d.resolve
d.resolve('someData1');

return d.promise();
}

function SomeFunction2(){
var d = $.Deferred();

//if I define callback here it will call immediately after resolve
//d.done(function(result){
//// do something with result
//});

//$.ajax() or $.get() or $('#someID').load() or whatever else or just d.resolve
d.resolve('someData2');

return d.promise();
}

...

function SomeFunctionN(){
var d = $.Deferred();

//if I define callback here it will call immediately after resolve
//d.done(function(result){
//// do something with result
//});

//$.ajax() or $.get() or $('#someID').load() or whatever else or just d.resolve
d.resolve('someDataN');

return d.promise();
}

然后在某些情况下我只需要调用这些函数中的一个,在另一些情况下需要调用其中两个,在第三种情况下我需要调用所有这些函数:

1: $.when(SomeFunction1()).done(function(result){
//callback defined inside SomeFunction1
});

2: $.when(SomeFunction1(), SomeFunction2()).done(function(result){
//callbacks defined inside SomeFunction1 and SomeFunction2
});

3: $.when(SomeFunction1(), SomeFunction2(), ... , SomeFunctionN()).done(function(result){
//callbacks defined inside SomeFunction1, ... , SomeFunctionN
});

成功和错误的回调应该在这些函数中定义,但只在所有调用的函数 return 结果之后执行。

可能吗?

您可以创建一个回调数组并在 $.when()

中循环它

var funcs = {},
  promises = [],
  callbacks = [];
// create 5 functions and promises 
for (var i = 0; i < 5; i++) {

  (function(i) {
    funcs[i] = function() {
      console.log('Data in func #' + (i + 1) + ':', this.data)
    }
    promises[i] = $.Deferred();
    // push specific callback for each promise into array
    promises[i].then(function(result) {
      callbacks[i] = funcs[i].bind({
        data: result
      })
    });
    // random resolve times
    setTimeout(function() {
      promises[i].resolve('Promise #' + (i + 1))
    }, Math.random() * 1500);

  })(i)

}



$.when.apply(null, promises).done(function() {
  console.log('start callbacks loop');
  callbacks.forEach(function(fn) {
    fn();
  })
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

由于这不是内置于承诺中的任何类型的行为,因此您可以实现自己的行为。 jQuery 将在特定承诺完成时调用 .done()(而不是在组承诺完成时),因此您必须以不同的方式指定回调。

一个方案是为每个承诺添加一个 .cb 属性,当整个组完成时,回调将被调用。然后,您可以创建 $.when 的超集,它将查找该回调并在整个组完成后调用它(如果存在):

$.whenAfter = function(promiseArray){
    return $.when.apply($, promiseArray).then(function() {
        var results = Array.prototype.slice.call(arguments);
        promiseArray.forEach(function(p, index) {
            if (p.cb) {
                p.cb(results[index]);
            }
        });
        return results;
    });
}

或者,如果您不想将 属性 添加到 promise 中,您可以传递一个与 promiseArray 相对应的单独回调数组:

$.whenAfter = function(promiseArray, callbackArray){
    return $.when.apply($, promiseArray).then(function() {
        var results = Array.prototype.slice.call(arguments);
        promiseArray.forEach(function(p, index) {
            var cb = callbackArray[index];
            if (cb) {
                cb(results[index]);
            }
        });
        return results;
    });
}

如果您的回调函数必须保留在您的函数内部,您不想在完成其他一些操作之前执行这些回调函数,那么我建议您传递一个承诺,然后在该回调函数执行时执行这些回调函数承诺已解决。

function SomeFunction1(p1){
     var p2 = $.ajax(...);
     // now wait for both our async operation and some other async operation
     // to be done before carrying out the rest of our business
     $.when(p1, p2).then(function(a1, a2) {
         // now everything else is done too so we can carry out the rest of our business
     });

     // return p2 so other things can know when this ajax operation is done
     return p2;
}

而且,您可以像这样组合多个:

var def = $.Deferred();
var p = def.promise();

$.when(SomeFunction1(p), SomeFunction2(p), SomeFunction3(p)).then(def.resolve, ref.reject);

我使用 promises 的经验告诉我这是丑陋的代码,但我现在不确定如何让这种特定类型的解决方案工作得更干净。


就我个人而言,我想我只是让 SomeFunctionX return 既是一个承诺又是一个回调,这样回调就可以从我们真正知道事情已经完成的外部调用:

function SomeFunction1(){
     var p = $.ajax(...);
     function callback() {
         // do something here after we're done and others are done too
     } 
     return {promise: p, callback: callback}
}

然后,在您想要调用多个函数时,将它们放在一个数组中并遍历该数组,收集结果并在适当的时候调用回调:

  var funcs = [SomeFunction1, SomeFunction2, SomeFunction3];

  var callbacks = [];
  var promises = funcs.map(function(fn) {
      var retVal = fn();
      callbacks.push(retVal.callback);
      return retVal.promise;
  });
  $.when.apply($, promises).then(function() {
      var args = Array.prototype.slice.call(arguments);
      callbacks.forEach(function(cb, index) {
          cb(args[index]);
      })
  });

并且,您可以将它变成一个可重复使用的函数,您只需传递一组函数,这些函数return编辑了正确的数据结构(承诺和回调):

function runAll(funcs) {
    var callbacks = [];
    var promises = funcs.map(function (fn) {
        var retVal = fn();
        // if it only returns only a thenable (not our data structure), then just return the promise
        // this allows you to mix in functions that just return a promise
        if (typeof retVal.then === "function") {
            // assume no callback
            callbacks.push(null);
            return retVal;
        }
        callbacks.push(retVal.callback);
        return retVal.promise;
    });
    return $.when.apply($, promises).done(function () {
        try {
            var args = Array.prototype.slice.call(arguments);
            callbacks.forEach(function (cb, index) {
                if (cb) {
                    cb(args[index]);
                }
            });
        } catch(e) {
            // if any callback throws an exception, then reject
            return $.Deferred().reject(e);
        }
    });
});

var funcs = [SomeFunction1, SomeFunction2, SomeFunction3];

runAll(funcs).done(function(results) {
    // all done here
}).fail(function(err) {
    // some sort of error here
});

P.S。如果您正在使用已经创建和 return 诸如 $.get() 之类的承诺的异步操作,那么您不应该创建自己的延迟。那是一个promise anti-pattern。您应该 return 已经创建的承诺。