如何避免Javascript / jQuery中的硬编码链式异步函数?

几乎我程序中的所有函数都有某种异步调用,但它们都依赖于某些先前函数的结果。 因此,我将下一个函数调用硬编码到每个函数中:

function getStuff() { $.ajax({ ... success: function(results) { // other functions involving results getMoreStuff(results); } }); } function getMoreStuff(results) { $.ajax({ ... success: function(moreResults) { // other functions involving moreResults doSomethingWithStuff(moreResults); } ); } 

等等。 它是一个大型链,每个函数调用下一个函数。 虽然这在程序中有效,但它使每个函数都无法单独使用。

我对如何避免这个问题有点失落。 我无法弄清楚如何使用通用回调函数,因为当我进行函数调用时,它会像这样结束(使用上面的函数):

 getStuff(function() { getMoreStuff(results, doSomethingWithStuff); }; 

但是,“结果”还没有定义。

解决方案似乎很明显,我只是对它有点密集。 抱歉!

概观

你有几个选择。 您可以使用回调来使用这些函数看起来像这样的代码:

 getStuff(function(results) { getMoreStuff(results, doSomethingWithStuff); }); 

或者像这样,使用jQuery的DeferredPromise对象:

 getStuff().then(getMoreStuff).then(doSomethingWithStuff): 

使用回调

getStuffgetMoreStuff接受一个参数,这个参数在完成后调用,例如:

 function getStuff(callback) { // ^------------------------------ callback argument $.ajax({ ... success: function(results) { // other functions involving results callback(results); // ^------------------------------------ use the callback arg } }); } 

…和getMoreStuff类似。

使用DeferredPromise

jQuery的ajaxfunction与其DeferredPromisefunction集成在一起。 您可以添加return现有function以使其工作,例如:

 function getStuff(callback) { return $.ajax({ ... }); } 

(注意:无需success回调。)

那么这段代码:

 getStuff().then(getMoreStuff).then(doSomethingWithStuff); 

做这个:

  1. getStuff启动其ajax调用并返回调用创建的Promise

  2. 当该ajax调用完成并解析promise时,将调用getMoreStuff ,并将ajax调用的结果作为其第一个参数。 它开始了它的 ajax调用。

  3. getMoreStuffajax调用完成时,将调用doSomethingWithStuff并调用调用的结果( getMoreStuff )。

重要的是使用then ,而不是done ,以便在每个阶段获得正确的结果。 (如果你使用donegetMoreStuff doSomethingWithStuff都会看到getStuffajax调用的结果。)

这是使用ajax的完整示例:

小提琴 | 替代摆弄ajax调用每个花一秒钟 (让你更容易看到发生了什么)

 function getStuff() { display("getStuff starting ajax") return $.ajax({ url: "/echo/json/", type: "POST", data: {json: '{"message": "data from first request"}'}, dataType: "json" }); } function getMoreStuff(results) { display("getMoreStuff got " + results.message + ", starting ajax"); return $.ajax({ url: "/echo/json/", type: "POST", data: {json: '{"message": "data from second request"}'}, dataType: "json" }); } function doSomethingWithStuff(results) { display("doSomethingWithStuff got " + results.message); } getStuff().then(getMoreStuff).then(doSomethingWithStuff); function display(msg) { var p = document.createElement('p'); p.innerHTML = String(msg); document.body.appendChild(p); } 

输出:

  getStuff启动ajax

 getMoreStuff从第一个请求获取数据,启动ajax

 doSomethingWithStuff从第二个请求获取数据 

你不需要使用ajax来获得这个好处,你可以使用你自己的DeferredPromise对象,这样你就可以编写这样的链:

 one().then(two).then(three); 

…对于您可能有异步完成的任何情况。

这是一个非ajax示例:

小提琴

 function one() { var d = new $.Deferred(); display("one running"); setTimeout(function() { display("one resolving"); d.resolve("one"); }, 1000); return d.promise(); } function two(arg) { var d = new $.Deferred(); display("Two: Got '" + arg + "'"); setTimeout(function() { display("two resolving"); d.resolve("two"); }, 500); return d.promise(); } function three(arg) { var d = new $.Deferred(); display("Three: Got '" + arg + "'"); setTimeout(function() { display("three resolving"); d.resolve("three"); }, 500); return d.promise(); } one().then(two).then(three); function display(msg) { var p = document.createElement('p'); p.innerHTML = String(msg); document.body.appendChild(p); } 

输出:

 一个人跑

一个解决

二:得到'一个'

两个解决

三:得到'两个'

三个解决 

必要时可以组合这两个( ajax示例和非ajax示例)。 例如,如果我们从ajax示例中获取getStuff ,并且我们决定在将数据getMoreStuffgetMoreStuff之前必须对数据进行一些处理,我们就会改变它: Fiddle

 function getStuff() { // Create our own Deferred var d = new $.Deferred(); display("getStuff starting ajax") $.ajax({ url: "/echo/json/", type: "POST", data: {json: '{"message": "data from first request"}', delay: 1}, dataType: "json", success: function(data) { // Modify the data data.message = "MODIFIED " + data.message; // Resolve with the modified data d.resolve(data); } }); return d; } 

请注意,我们如何使用它并没有改变:

 getStuff().then(getMoreStuff).then(doSomethingWithStuff); 

所有改变都在getStuff

这是关于整个“承诺”概念的一个伟大的事情(它完全不是jQuery特有的,但是jQuery为我们提供了方便的版本),它非常适合解耦。

尝试

 function getStuff() { return $.ajax({ ... success: function(results) { // other functions involving results } }); } function getMoreStuff(results) { return $.ajax({ ... success: function(moreResults) { // other functions involving moreResults } ); } 

然后

 getStufff().done(function(){ getMoreStuff().done(doSomethingWithStuff) }) 

等等

传递接受参数的回调:

 function getStuff( callback ) { $.ajax({ ... success: function(results) { // callback with result callback(results); } }); } function getMoreStuff(results, callback) { $.ajax({ ... success: function(moreResults) { // callback with result callback(moreResults); } ); } function doSomethingWithStuff(results, callback) { // process results via some means not described herein :) if (callback){ // callback yet again with results, but only if callback provided this time callback(stillMoreResults); } } 

然后使用类似的东西:

 getStuff(function(results) { getMoreStuff(results, function(moreresults){ doSomethingWithStuff(moreresults); }); }; 

此模式通常对任何异步操作都有用。 它不是特定于Ajax调用(我用它在JQuery中创建一个完整的动画棋盘游戏)。

解决方案非常简单。 您必须使用Publish–subscribe模式。 使用jQuery最简单的实现:

 $('body').trigger('joined-game', [game_id, response]); 

第一个参数是您要发布的事件名称,第二个参数是数据数组。

最佳实践是在最具体的DOM元素上触发事件,但如果您在多个页面上订阅相同的事件,并且不确定所有页面上是否存在DOM元素,则可以在body或某些“转储/合成”上触发它“不可见的DOM元素始终存在于所有页面上。

 $("body").on('joined-game', function(event, game_id, response){ //... }); 

然后您订阅您想要使用的活动。 请记住,除了您的数据,第一个参数始终是事件。

此解决方案的另一个优点是您可以将代码拆分为多个文件。

更多细节: http : //webility.pl/en/blog/entry/chaining-javascript-functions-without-dependecy-hell