当任何jQuery Deferred失败时退出for循环

时间:2014-11-24 08:32:00

标签: jquery-deferred

在下面的代码中,每当任何一个延迟对象失败时,我都需要退出for循环。每当循环大写redyellow颜色失败并且我想避免其他延迟被执行。

var colours = ['violet', 'indigo', 'blue', 'green', 'yellow', 'orange', 'red'];

var capitalize = function(text) {
  var deferred = $.Deferred(),
    delay = (1 + Math.floor(Math.random() * 5)) * 500;

  setTimeout(function() {
    if (text === 'red' || text === 'yellow') {
      deferred.reject('error colour', true);
    } else {
      deferred.resolve(text.toUpperCase());
    }
  }, delay);

  return deferred.promise();
}

var deferreds = [],
  result = [];

for (var i = 0; i < colours.length; i++) {
  var deferred = capitalize(colours[i]);

  deferred.done(function(t) {
    console.log(t);
    result.push(t);
  }).fail(function(e) {
    console.log(e);
  });

  deferreds.push(deferred);
}

$.when.apply($, deferreds).done(function(){
    console.log(result);
}).fail(function(e){
    console.log(e);
});

Link to jsfiddle

修改

我正在建立一个基于Cordova的移动应用程序,带有主干。我使用localStorage进行持久化,并且在持久化之前加密所有数据。虽然使用localstorage存储/检索数据是同步的,但是为了加密/解密数据,我调用异步的本机设备API。我想过使用Backbone.localstorage插件,但它不是为异步开发的......所以没那么有用!

我正在开发自己的插件。除了一种方法,findAll之外,我几乎完成了大部分代码。

findAll方法返回存储在localstorage中的所有模型。

// exiting method in the Backbone.Localstorage plugin
extend(Backbone.LocalStorage.prototype, {
  // ...
  findAll: function() {
    var result = [];
    for (var i = 0, id, data; i < this.records.length; i++) {
      id = this.records[i];
      data = this.serializer.deserialize(this.localStorage().getItem(this._itemName(id)));
      if (data != null) result.push(data);
    }
    return result;
  }
});

在localstorage中,所有模型都被加密并存储为字符串。在反序列化回模型之前,我已经解密了它们。 this.crypto类解密从localstorage返回的字符串并反序列化回模型。加密/解密是aysnc,因此它返回一个promise。

// Method in my new plugin
findAll: function () {
  var deferred = $.Deferred(),
    deferreds = [],
    result = [], 
    errors = [];

  // this.records contains all the model ids
  for (var i = 0, id, data; i < this.records.length; i++) {
    id = this.records[i];

    // this.crypto return a promise with the backbone model as parameter
    var d = this.crypto.deserialize(this.localStorage().getItem(this._itemName(id)));

    d.done(function(model) {
      // I've to maintain the order in result so storing the index
      result.push({ index: i, model: model });
    }).fail(function(error) {
      errors.push(error);
    });

    deferreds.push(d);
  }

  $.when.apply($, deferreds)
    .done(function() {
      // TODO: sort the result based on i and resolve it
      deferred.resolve(sortedResult);
    })
    .fail(...);

  return deferred.promise();
}

解密时可能会出现错误,在这种情况下,我可以停止整个过程。

2 个答案:

答案 0 :(得分:1)

  

“只要......它失败了,我想避免其他延期执行”

暂时考虑一下。这是不可能的。

  • 您有一个for循环,用于创建和运行异步函数(即“延迟”)。
  • 即使第一个函数返回(即“解析”),循环也会完成 long
  • 当这些函数中的第一个以负结果返回时(即“承诺被拒绝”),此时你可以做些什么来阻止其他函数执行?已经为时已晚。

有几种可能的解决方案,但它们都依赖于更好的问题定义。

答案 1 :(得分:1)

您的新findAll方法将简化如下:

findAll: function () {
    var promises = this.records.map(function(record) {
        return this.crypto.deserialize(this.localStorage().getItem(this._itemName(record)));
    });
    return $.when.apply(null, promises).then(function() {
        return Array.prototype.slice.apply(arguments);//convert arguments to array
    });
}

成功后,这将按照您想要的顺序返回一系列结果的承诺 - 无需对任何内容进行排序。任何单独的异步失败都将导致返回的承诺被拒绝。但是,这种拒绝为时已晚,无法阻止已经发出的其他异步调用。

为了允许无法禁止进一步调用this.crypto.deserialize(...),您必须以串行方式(以菊花链形式)执行调用,从而允许每次调用的结果确定下一步操作。< / p>

findAll: function () {
    var results = [];
    return this.records.reduce(function(promise, record) {
        return promise.then(function(result) {
            if(result) results.push(result);
            return this.crypto.deserialize(this.localStorage().getItem(this._itemName(record)));
        });
    }, $.when()).then(function() {
        return results;
    });
}

和以前一样,成功后,这将按照您想要的顺序返回一系列结果的承诺。

由于每个阶段仅在前一阶段成功时执行,因此在异步失败时将自动禁止进一步调用。虽然.reduce(...)无条件地建立了一个链条,但拒绝将迫使链条沿着其失败路径 - 失败的权利将被跳过(它们没有失败的处理程序)。

如果您需要根据测试result强制失败,情况会略有不同,但整体模式基本相同。