【问题标题】:Multiple Sequential Async JavaScript Functions多个顺序异步 JavaScript 函数
【发布时间】:2015-01-03 00:43:55
【问题描述】:

假设我有一个如下所示的函数:

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

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

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

  callback(final);
});

显然,这并没有达到我想要的效果(当它同时具有 x 和 y 时,在 final 上调用 callback)。我有几个问题:

  1. 有没有办法在不嵌套所有内容的情况下做我想做的事情?
  2. 当前表单是否引入了竞争条件?两个异步函数是否访问相同的final

【问题讨论】:

  • 两个异步函数都访问相同的final。但是,它们的结果不会传递给callback 函数,因此final.xfinal.y 基本上丢失了。
  • 你要么嵌套回调,要么使用promise,否则无法知道异步函数已经完成。
  • @adeneo,这里有没有一种简单的使用promise的方法,不用重写asyncFuncOne/asyncFuncTwo?
  • 这取决于他们是否已经返回了一个 Promise,否则你必须实际编写代码来从函数中返回一个 Promise,或者在回调等中解析一个已经创建的 Promise。

标签: javascript asynchronous race-condition


【解决方案1】:

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

实际上,您的代码喜欢用承诺重写。相信我,这种重构是你 100% 需要的。但是好的,让我们尝试在不调用承诺的情况下解决这个特定问题——就像一个练习一样。实际上,在 Promise 时代之前,模式是引入了一个特殊的函数来检查我们是否可以认为我们已经完成了。

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

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);

});

您可以继续努力使这种方法更加灵活和通用,但请再次相信我, 你最终会得到与 promises 非常相似的东西,所以会有更好的 promises ;)

方法 #1。承诺现有功能

如果由于某种原因,您还没有准备好将所有函数从回调样式重写为 Promise, 您可以再次使用装饰器来承诺现有功能。以下是所有现代浏览器中都存在的原生 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 是无与伦比的。几乎总是值得尝试以基于 Promise 的样式重写现有代码。下面是这样的代码的样子

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

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

}).then(callback);

看看它变得多么好。此外,使用 Promise 的一个常见错误 - 是有一个连续的 then 瀑布 - 检索第一块数据,只有在那之后 - 第二个,在那之后 - 第三个。实际上,您永远不应该这样做,除非您根据您在之前的一个请求中获得的内容来转换在第 N 个请求中收到的数据 - 而只是使用 all 方法。

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

旁注:截至 2014 年 12 月,除了 IE 之外,所有主流现代浏览器都原生支持原生 Promise,并且在 Node.js 中,原生 Promise 支持是从 0.11.13 版本开始的,所以在现实生活中你仍然最可能需要使用 promise 库。有很多 Promise 规范的实现,你可以查看 this page 获取独立 Promise 库的列表,它相当大,我猜最流行的解决方案是 Q 和 bluebird。

方法#3。发电机。我们光明的未来。嗯,可能是

值得一提的是,Firefox、基于 Chromium 的浏览器和 node.js(使用 --harmony_generators 选项调用)事实上支持生成器。因此,事实上,在某些情况下,生成器可以在生产代码中使用,并且实际上已经在使用。只是如果你正在编写一个通用的网络应用程序,你应该知道这种方法,但你可能暂时不会使用它。因此,您可以使用 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

【讨论】:

  • 对于这个简单的问题,我不同意 Promises,甚至方法 #0 中的重构是必要的——甚至更好。但我为 Promise.all 的使用 +1。您还应该提到,并非所有浏览器都原生支持 Promises,无论是现代浏览器还是其他浏览器。
  • @rgthree,除了 IE,几乎所有现代浏览器都支持原生 Promise,将其添加到答案中。
  • 谢谢。我知道 IE 缺乏,但是“除了 IE”并不是所有现代浏览器(尤其是拥有大部分市场份额的浏览器)的确切含义。此外,它们也不受 Android KitKat 或 iOS 7.1 及更早版本(两者的市场份额都比其新版本更多)的原生支持。事实上,全球 40% 的浏览器用户没有使用原生支持 Promises 的浏览器。我认为在一个好的答案中不提及(或推荐您添加的库)是愚蠢的。
【解决方案2】:

另一种解决方案是创建一个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】:

    final.xfinal.y 设置为 final,但 之后它被发送到 callback 所以,除非回调正在等待,否则 xy 在回调接收时未定义他们。

    您可以检查一个是否在其他人的响应中返回并调用回调:

    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);
          }
      });
    });
    

    您可以嵌套回调,但这会导致 asyncfuncTwoasyncfuncOne 完成之前不会被调用):

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

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

    如果你想看看它会是什么样子,那就是:

    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); 
    });
    

    注意没有回调,只使用then。在实际场景中,您还可以使用catchreject。在https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise 阅读有关 Promises 的更多信息,但是,我个人认为没有强烈需要将它们用于这个单一的、特定的问题(但是,对于他们自己的问题)。

    【讨论】:

    • 你不需要依次调用 asyncFuncOne 和 asyncFuncTwo。
    【解决方案4】:

    一个非常糟糕的主意,但我之前不得不使用它,因为我不打算为单个函数导入 50k 的承诺库,而是设置一个循环超时来检查是否所有必需的设置变量,然后调用回调。

    【讨论】:

    • 这确实是个坏主意,因为这些函数显然有可以替代使用的回调。
    • 当然,拥有可以使用的回调函数对这个函数没有任何帮助,因为您需要两个回调都成功才能继续。在没有承诺的 Javascript 中,如果不这样做,或者在两个回调中显式设置检查以查看另一个回调是否首先完成,您就无法做到这一点。 .. 就个人而言,我认为让一个回调依赖于另一个回调的完成也是一个糟糕的主意,如果不是更糟的话。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-08-30
    • 1970-01-01
    相关资源
    最近更新 更多