如何避免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的Deferred
和Promise
对象:
getStuff().then(getMoreStuff).then(doSomethingWithStuff):
使用回调
让getStuff
和getMoreStuff
接受一个参数,这个参数在完成后调用,例如:
function getStuff(callback) { // ^------------------------------ callback argument $.ajax({ ... success: function(results) { // other functions involving results callback(results); // ^------------------------------------ use the callback arg } }); }
…和getMoreStuff
类似。
使用Deferred
和Promise
jQuery的ajax
function与其Deferred
和Promise
function集成在一起。 您可以添加return
现有function以使其工作,例如:
function getStuff(callback) { return $.ajax({ ... }); }
(注意:无需success
回调。)
那么这段代码:
getStuff().then(getMoreStuff).then(doSomethingWithStuff);
做这个:
-
getStuff
启动其ajax
调用并返回调用创建的Promise
。 -
当该
ajax
调用完成并解析promise时,将调用getMoreStuff
,并将ajax
调用的结果作为其第一个参数。 它开始了它的ajax
调用。 -
当
getMoreStuff
的ajax
调用完成时,将调用doSomethingWithStuff
并调用该调用的结果(getMoreStuff
)。
重要的是使用then
,而不是done
,以便在每个阶段获得正确的结果。 (如果你使用done
, getMoreStuff
和 doSomethingWithStuff
都会看到getStuff
的ajax
调用的结果。)
这是使用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
来获得这个好处,你可以使用你自己的Deferred
和Promise
对象,这样你就可以编写这样的链:
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
,并且我们决定在将数据getMoreStuff
给getMoreStuff
之前必须对数据进行一些处理,我们就会改变它: 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