Javascript - 异步调用后同步

old*_*kes 20 javascript asynchronous callback synchronous

我有一个Javascript对象,需要2次调用外部服务器来构建其内容并执行任何有意义的操作.构建对象使得实例化它的实例将自动进行这两个调用.2个调用共享一个公共回调函数,该函数对返回的数据进行操作,然后调用另一个方法.问题是在两个方法都返回之前不应调用下一个方法.这是我目前实现的代码:

foo.bar.Object = function() {
this.currentCallbacks = 0;
this.expectedCallbacks = 2;

this.function1 = function() {
    // do stuff
    var me = this;
    foo.bar.sendRequest(new RequestObject, function(resp) {
        me.commonCallback(resp);
        });
};

this.function2 = function() {
    // do stuff
    var me = this;
    foo.bar.sendRequest(new RequestObject, function(resp) {
        me.commonCallback(resp);
        });
};

this.commonCallback = function(resp) {
    this.currentCallbacks++;
    // do stuff
    if (this.currentCallbacks == this.expectedCallbacks) {
        // call new method
    }
};

this.function1();
this.function2();
}
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,我强制对象在两个调用返回之后继续使用一个简单的计数器来验证它们都已返回.这可行,但似乎是一个非常差的实现.我现在只使用Javascript几周了,我想知道是否有更好的方法来做同样的事情,我还没有偶然发现.

感谢您的帮助.

Bry*_*yle 13

除非你愿意序列化AJAX,否则没有其他方法可以让你想到做你提出的建议.话虽这么说,我认为你所拥有的是相当不错的,但你可能想要清理一下结构,以免乱丢你用初始化数据创建的对象.

这是一个可以帮助您的功能:

function gate(fn, number_of_calls_before_opening) {
    return function() {
        arguments.callee._call_count = (arguments.callee._call_count || 0) + 1;
        if (arguments.callee._call_count >= number_of_calls_before_opening)
            fn.apply(null, arguments);
    };
}
Run Code Online (Sandbox Code Playgroud)

这个函数就是所谓的高阶函数 - 一个将函数作为参数的函数.此特定函数返回一个函数,该函数在被调用number_of_calls_before_opening次数时调用传递的函数.例如:

var f = gate(function(arg) { alert(arg); }, 2);
f('hello');
f('world'); // An alert will popup for this call.
Run Code Online (Sandbox Code Playgroud)

你可以使用它作为你的回调方法:

foo.bar = function() {
    var callback = gate(this.method, 2);
    sendAjax(new Request(), callback);
    sendAjax(new Request(), callback);
}
Run Code Online (Sandbox Code Playgroud)

第二个回调,无论哪个都将确保method被调用.但这会导致另一个问题:gate函数调用传递的函数而没有任何上下文,这意味着this将引用全局对象,而不是您正在构造的对象.有几种方法可以解决这个问题:您可以this通过别名将其转换为meself.或者你可以创建另一个更高阶的函数来做到这一点.

这是第一种情况:

foo.bar = function() {
    var me = this;        
    var callback = gate(function(a,b,c) { me.method(a,b,c); }, 2);

    sendAjax(new Request(), callback);
    sendAjax(new Request(), callback);
}
Run Code Online (Sandbox Code Playgroud)

在后一种情况下,其他更高阶函数将类似于以下内容:

function bind_context(context, fn) {
    return function() {
        return fn.apply(context, arguments);
    };
}
Run Code Online (Sandbox Code Playgroud)

此函数返回一个函数,该函数在传递的上下文中调用传递的函数.它的一个例子如下:

var obj = {};
var func = function(name) { this.name = name; };
var method = bind_context(obj, func);
method('Your Name!');
alert(obj.name); // Your Name!
Run Code Online (Sandbox Code Playgroud)

从透视角度来看,您的代码如下所示:

foo.bar = function() {
    var callback = gate(bind_context(this, this.method), 2);

    sendAjax(new Request(), callback);
    sendAjax(new Request(), callback);
}
Run Code Online (Sandbox Code Playgroud)

在任何情况下,一旦你完成了这些重构,你就会清除正在构建的对象,它只是初始化所需的所有成员.


dan*_*ani 8

我可以补充一点,Underscore.js有一个很好的小助手:

创建一个只在首次被调用计数次数后才能运行的函数版本.在继续之前,您可以将异步响应分组,以确保所有异步调用都已完成.

_.after(count, function)
Run Code Online (Sandbox Code Playgroud)

代码_after(作为版本1.5.0):

_.after = function(times, func) {
  return function() {
    if (--times < 1) {
      return func.apply(this, arguments);
    }
  };
};
Run Code Online (Sandbox Code Playgroud)

许可证信息(版本1.5.0)


Nor*_*rtl 5

拥有这个计数器几乎没有其他办法.另一种选择是使用对象{}并为每个请求添加一个键,如果完成则将其删除.通过这种方式,您可以立即知道返回的内容.但解决方案保持不变.

您可以稍微更改代码.如果在你的例子中你只需要调用commonCallback中的另一个函数(我称之为otherFunction),而不需要使用commonCallback.为了保存上下文,您已经使用了闭包.代替

foo.bar.sendRequest(new RequestObject, function(resp) {
            me.commonCallback(resp);
            });
Run Code Online (Sandbox Code Playgroud)

你可以这样做

foo.bar.sendRequest(new RequestObject, function(resp) {
            --me.expectedCallbacks || me.otherFunction(resp);
            });
Run Code Online (Sandbox Code Playgroud)