如何在循环中处理异步javascript?

时间:2013-09-06 14:21:36

标签: javascript

我有一个像这样的forloop:

for (var name in myperson.firstname){
  var myphone = new phone(myperson, firstname);
    myphone.get(function(phonenumbers){
      if(myphone.phonearray){
      myperson.save();
      //Can I put a break here?; 
    }
  });
}

它的作用是根据各种名字在数据库中搜索电话号码。我想要实现的是,一旦找到与任何名字相关联的数字,它就会执行myperson.save,然后停止所有迭代,这样就不会保存重复项。有时,没有一个名字会返回任何电话号码。

myphone.get包含服务器请求,成功时会触发回调

如果我在响应中放置一个中断,那么循环的其他迭代会发生什么?其他http请求很可能已经启动。我不希望他们执行保存。我想到的一个解决方案是将一个变量置于forloop之外并将其设置为save,然后检查其他回调何时被触发,但我不确定这是否是最好的方法。

2 个答案:

答案 0 :(得分:1)

您可以编写一个辅助函数来限制调用:

function callUntilTrue(cb) {
    var done = false;
    return function () {
        if (done) {
            log("previous callback succeeded. not calling others.");
            return;
        }
        var res = cb.apply(null, arguments);
        done = !! res;
    };
}

var myperson = {
    firstname: {
        "tom": null,
        "jerry": null,
        "micky": null
    },
    save: function () {
        log("save " + JSON.stringify(this, null, 2));
    }
};

var cb = function (myperson_, phonenumbers) {
    if (myperson_.phonearray) {
        log("person already has phone numbers. returning.");
        return false;
    }
    if (phonenumbers.length < 1) {
        log("response has no phone numbers. returning.");
        return false;
    }
    log("person has no existing phone numbers. saving ", phonenumbers);
    myperson_.phonearray = phonenumbers;
    myperson_.save();
    return true;
};

var restrictedCb = callUntilTrue(cb.bind(null, myperson));

for (var name in myperson.firstname) {
    var myphone = new phone(myperson, name);
    myphone.get(restrictedCb);
}

示例控制台:

results for tom-0 after 1675 ms
response has no phone numbers. returning.
results for jerry-1 after 1943 ms
person has no existing phone numbers. saving , [
  "jerry-1-0-number"
]
save {
  "firstname": {
    "tom": null,
    "jerry": null,
    "micky": null
  },
  "phonearray": [
    "jerry-1-0-number"
  ]
}
results for micky-2 after 4440 ms
previous callback succeeded. not calling others.

Full example in this jsfiddle假冒超时。

编辑添加了HTML输出以及console.log

答案 1 :(得分:0)

第一个结果回调只会在循环之后发生,因为javascript的单线程性质,并且因为如果事件到达,运行代码不会中断。

如果您仍然希望并行发出请求,则可以使用标记

var saved = false;
for (var name in myperson.firstname){
  var myphone = new phone(myperson, firstname /* name? */);
  myphone.get(function(phonenumbers){
    if (!saved && myphone.phonearray){
      saved = true;
      myperson.save();
    }
  });
}

这不会取消任何待处理的请求,但是,只要他们返回就阻止保存。 如果您的.get()会返回可取消的内容(可能是请求本身),那会更好。

var saved = false;
var requests = [];
for (var name in myperson.firstname){
  var myphone = new phone(myperson, firstname /* name? */);
  var r;
  requests.push(r = myphone.get(function(phonenumbers){
    // Remove current request.
    requests = requests.filter(function(i) {
      return r !== i;
    });
    if (saved || !myphone.phonearray) {
      return;
    }
    saved = true;
    // Kill other pending/unfinished requests.
    requests.forEach(function(r) {
      r.abort();
    });
    myperson.save();
  }));
}

更好的是,不要立即启动所有请求。而是构造一个包含所有可能组合的数组,有一个计数器(信号量)并且只启动X请求。

var saved = false;
var requests = [];
// Use requests.length as the implicit counter.
var waiting = []; // Wait queue.
for (var name in myperson.firstname){
  var myphone = new phone(myperson, firstname /* name? */);
  var r;
  if (requests.length >= 4) {
    // Put in wait queue instead.
    waiting.push(myphone);
    continue;
  }
  requests.push(r = myphone.get(function cb(phonenumbers){
    // Remove current request.
    requests = requests.filter(function(i) {
      return r !== i;
    });
    if (saved) {
      return;
    }
    if (!myphone.phonearray) {
      // Start next request.
      var w = waiting.shift();
      if (w) {
        requests.push(w.get(cb));
      )
      return;
    }
    saved = true;
    // Kill other pending/unfinished requests.
    requests.forEach(function(r) {
      r.abort();
    });
    myperson.save();
  }));
}