多个顺序异步JavaScript函数

时间:2014-12-24 23:11:37

标签: javascript asynchronous race-condition

我们说我有一个看起来像这样的功能:

var foo = function(callback) {
  var final = {};

  asyncFuncOne(function(x) {
    final.x = x;
  });

  asyncFuncTwo(function(y) {
    final.y = y;
  });

  callback(final);
});

显然,这并不是我想做的事情(当callback同时有x和y时调用final。我有几个问题:

  1. 有没有办法在没有筑巢的情况下做我想做的事情?
  2. 目前的表格会引入竞争条件吗?两个异步函数是否都访问相同的final

4 个答案:

答案 0 :(得分:1)

方法#0。没有承诺的痛苦生活。生活

实际上,你的代码就好像在承诺中重写一样。相信我,这种重构是你100%需要的。但是好吧,让我们尝试解决这个特殊的问题,而不是一直调用承诺 - 就像练习一样。实际上在承诺时代之前,模式是引入一个特殊功能,检查我们是否可以认为我们已经完成了。

在您的特定情况下,此类功能是:

function weAreDone() {
   return final.hasOwnPropery('x') && final.hasOwnProperty('y')
}

然后我们可以介绍asyncFuncDecorator:

function asyncFuncDecorator = function(asyncFunc, asyncFuncHandler) {
   return function(doneFunc, doneHandler) {
       asyncFunc(asyncFuncHandler);
       if (doneFunc()) {
          doneHandler();
       }
   }
}

通过介绍这两个函数,您可以编写如下内容:

var foo = function(callback) {
  var final = {};

  //here goes abovementioned declarations
  ... 

  asyncFuncDecorator(asyncFuncOne, function(x) {
    final.x = x;
  })(weAreDone, callback);

  asyncFuncDecorator(asyncFuncTwo, function(y) {
    final.y = y;
  })(weAreDone, callback);

});

你可以继续努力使这种方法更加灵活和普遍,但再次相信我, 你会得到与承诺非常相似的东西,所以更好的承诺;)

方法#1。宣传现有职能

如果出于某种原因,你还没有准备好将所有功能从回调样式重写为承诺, 你可以再次使用装饰器来宣传现有的功能。以下是本地Promise的完成方式,它已经存在于所有现代浏览器中(对于替代方案,请检查this question):

function promisify(asyncCall){
    return new Promise(function(resolve,reject){
         asyncCall(resolve,reject);
    });
}

在这种情况下,您可以用这种方式重写代码:

var foo = function(callback) {

      //here goes abovementioned declarations
      ... 

      Promise.all([promisify(asyncFuncOne), promisify(asyncFuncTwo)]).then(function(data) {
          // by the way, I'd rather not to call any variable "final" ))
          final.x = data[0];
          final.y = data[1];
      }).then(callback);

    });

不是说实际上foo最好是自己被宣传;)

方法#2。无处不在。从一开始

值得重申一下这个想法 - 只要你需要在N个其他异步函数完成后触发某个函数 - 99%的情况下的承诺是无与伦比的。几乎总是值得尝试以基于承诺的风格重写现有代码。这是代码如何看起来像

Promise.all([asyncFuncOne(), asyncFuncTwo()]).then(function(data) {

  return Promise.resolve({
    x: data[0],
    y: data[1] 
  })

}).then(callback);

看看它变得多好。此外,使用promises的一个常见错误 - 是有一个顺序的thens瀑布 - 只在那之后检索第一块数据 - 第二个,之后 - 第三个。实际上你永远不应该这样做,除非你正在转换第N个请求中收到的数据,具体取决于你之前的一个请求中的内容 - 而只是使用 all 方法。

理解这一点至关重要。这是承诺经常被误解为过于复杂的主要原因之一。

旁白:从14年12月开始,除了IE之外,所有主要的现代浏览器都本机支持本机Promise,并且在Node.js中有本机承诺支持,因为版本0.11.13,所以在现实生活中你仍然是最可能需要使用promise库。有很多Promise规范实现,你可以查看this page的独立承诺库列表,它非常大,最流行的解决方案是,我猜,Q和蓝鸟。

方法#3。发电机。我们美好的未来。好吧,可能

值得一提的是,Firefox,基于Chromium的浏览器和node.js(使用--harmony_generators选项调用)事实上支持生成器。因此,事实上,在生产代码中,有些情况下可以使用生成器,并且实际上已经使用了生成器。只是如果你正在编写一个通用的Web应用程序,你应该知道这种方法,但你可能暂时不会使用它。因此,您可以使用js中的生成器允许您通过yield / iterator.next()调用双向通信这一事实。在那种情况下。

function async(gen) {
    var it = gen();
    var state = it.next();

    var next = function() {
        if (state.done) {
            return state.value;
        };  
        state.value(function(res) {
            state = it.next(res);   
            next();
        }); 
    }   

    next();
}

async(function* () {
    var res = { 
        x: yield asyncFuncOne,
        y: yield asyncFuncTwo
    }   

    callback(res);
});

实际上,已有数十家图书馆为您做这个生成器包装工作。 您可以阅读有关此方法和相关库here的更多信息。

答案 1 :(得分:0)

final.xfinal.y设置为final,但之后发送到callback,因此,除非回调正在等待,{{

,当回调接收它们时,1}}和x未定义。

您可以查看是否有人回复其他人的回复并呼叫回调:

y

您可以嵌套回调,但这会导致在var foo = function(callback) { var final = {}; asyncFuncOne(function(x) { final.x = x; if (typeof final.y !== 'undefined') { callback(final); } }); asyncFuncTwo(function(y) { final.y = y; if (typeof final.x !== 'undefined') { callback(final); } }); }); 完成之前不会调用asyncfuncTwo

asyncfuncOne

然后是Promises。这些是异步的未来,但是并非所有浏览器都支持它们(即IE的所有IE [11及以下])。事实上, 40%的浏览器用户没有使用本机支持Promises的浏览器。这意味着您必须使用polyfill库来支持向页面添加大量文件大小。对于这个简单的问题,在这个给定的时间,我不建议使用Promises来解决这个简单的问题。但是,你一定要仔细阅读它们的使用方法。

如果你想看看它的样子,那就是:

var foo = function(callback) {
  var final = {};

  asyncFuncOne(function(x) {
      final.x = x;
      asyncFuncTwo(function(y) {
        final.y = y;
        callback(final);
      });
  });
});

注意没有回调,只需使用var asyncFuncOne = function() { return new Promise(function(resolve, reject) { // A 500 seconds async op and resolve x as 5 setTimeout(function() { resolve(5); }, 500); }); }; var asyncFuncTwo = function() { return new Promise(function(resolve, reject) { // A 750ms async op and resolve y as 10 setTimeout(function() { resolve(10); }, 750); }); }; var foo = function() { var final = {}; return new Promise(function(resolve, reject) { Promise.all([ asyncFuncOne(), asyncFuncTwo() ]).then(function(values) { final.x = values[0]; final.y = values[1]; resolve(final); }); }); }; foo().then(function(final) { // After foo()'s Promise has resolved (750ms) console.log(final.x + ', ' + final.y); }); 。在实际情况中,您还可以使用thencatch。在这里阅读更多关于Promises的信息https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise但是,我个人并不认为有必要将它们用于这个单一的特定问题(但是,对于他们自己的问题)。

答案 2 :(得分:0)

另一个解决方案是创建一个setter:

var foo = function (callback) {
    var final = {
        setter: function(attr,value){
            this[attr] = value;
            if (this.hasOwnProperty("x") && this.hasOwnProperty("y"))
                callback(this);
        }
    };

    asyncFuncOne(function(x) {
        final.setter("x", x);
    });

    asyncFuncTwo(function(y) {
        final.setter("y", y);
    });
};

答案 3 :(得分:-2)

一个非常糟糕的主意,但我以前不得不使用它,因为我不打算为单个函数导入一个50k的promise库,就是设置一个循环的Timeout来检查看看如果设置了所有必需的变量,然后调用回调。