Javascript 在返回之前等待异步完成

Javascript 在返回之前等待异步完成,javascript,asp.net-mvc,knockout.js,durandal,amd,Javascript,Asp.net Mvc,Knockout.js,Durandal,Amd,这段代码返回一个config对象,并最终用knockout可观测值填充它 define("config", [], function () { var config = {}; $.ajax({ url: "api/Config", success: function (result) { for (pname in result) config[pname] = ko.observable(result[pname]); }, }

这段代码返回一个config对象,并最终用knockout可观测值填充它

define("config", [], function () {
  var config = {};
  $.ajax({
    url: "api/Config",
    success: function (result) {
      for (pname in result)
        config[pname] = ko.observable(result[pname]);
    },
  });
  return config;
});
它的工作原理是用设置填充config对象,但直到我的一个视图应用了绑定之后才完成,这会在运行时导致问题

在返回配置对象之前,如何设置它以等待ajax查询的结果?我的第一个想法是使用promise对象,但我不知道如何应用它

我不是在寻找解决办法,我已经有了:

define("config", [], function () {
  var config = {
    IsReady: ko.observable()
  };
  $.ajax({
    url: "api/Config",
    success: function (result) {
      jquery.extend(config, result);
      config.IsReady(true);
    },
  });
  return config;
});
有了这个设置,我像这样绑定

<img data-bind="attr: { src: config.IsReady() ? config.Logo : '' }" />
这会运行,但问题是如何让应用程序的其余部分等待,直到发生这种情况。因此,我将main.js的其余部分放在success函数中,如下所示

$.ajax({
  url: "api/Config",
  success: function (result) {
    define("config", [], result);
    define(['durandal/system', 'durandal/app', 'durandal/viewLocator'], 
           function (system, app, viewLocator) {
      //>>excludeStart("build", true);
      system.debug(true);
      //>>excludeEnd("build");

      app.title = 'Jumbo File Transfer';

      app.configurePlugins({
        router: true,
        dialog: true,
        widget: true
      });

      app.start().then(function () {
        //Replace 'viewmodels' in the moduleId with 'views' to locate the view.
        //Look for partial views in a 'views' folder in the root.
        viewLocator.useConvention();

        //Show the app by setting the root view model for our app with a transition.
        app.setRoot('viewmodels/shell', 'entrance');
      });
    });
  }
});
var foo;

$.ajax({ 
  url:"whatever", 
  async: false
}).done(function(result){ 
  foo = result;
});

//code that needs result
这实际上是按照正确的顺序执行的——我已经完成了。但应用程序无法启动。如果我不得不猜测原因,我会说define希望
这个
成为全局上下文

Nathan下面的回答并没有阻止Durandal正确启动,但config define的行为并不完全正确。我需要将config定义为一个充满设置属性的对象,而不是一个带有工厂方法的对象。但我们就快到了。它只需要像这样:

define('configFactory', ['plugins/http'], function (http) {
  "use strict";
  var getConfig = function () { 
    return http.get("api/Config").then(function (data) { return data; });
  };
  return { getConfig: getConfig };
});
define(['durandal/system', 'durandal/app', 'durandal/viewLocator', 'configFactory'], 
       function (system, app, viewLocator, configFactory) {

  //>>excludeStart("build", true);
  system.debug(true);
  //>>excludeEnd("build");

  app.title = 'Jumbo File Transfer';

  app.configurePlugins({
    router: true,
    dialog: true,
    widget: true
  });

  configFactory.getConfig().then(function (config) {
    define('config', [], config);
    app.start();
  }).then(function () {
    //Replace 'viewmodels' in the moduleId with 'views' to locate the view.
    //Look for partial views in a 'views' folder in the root.
    viewLocator.useConvention();

    //Show the app by setting the root view model for our app with a transition.
    app.setRoot('viewmodels/shell', 'entrance');
  });
});

事后看来 您可以通过将async选项设置为false来生成ajax块,如下所示

$.ajax({
  url: "api/Config",
  success: function (result) {
    define("config", [], result);
    define(['durandal/system', 'durandal/app', 'durandal/viewLocator'], 
           function (system, app, viewLocator) {
      //>>excludeStart("build", true);
      system.debug(true);
      //>>excludeEnd("build");

      app.title = 'Jumbo File Transfer';

      app.configurePlugins({
        router: true,
        dialog: true,
        widget: true
      });

      app.start().then(function () {
        //Replace 'viewmodels' in the moduleId with 'views' to locate the view.
        //Look for partial views in a 'views' folder in the root.
        viewLocator.useConvention();

        //Show the app by setting the root view model for our app with a transition.
        app.setRoot('viewmodels/shell', 'entrance');
      });
    });
  }
});
var foo;

$.ajax({ 
  url:"whatever", 
  async: false
}).done(function(result){ 
  foo = result;
});

//code that needs result
然而,引入瓶颈很少是个好主意

下面是一个基本问题:下面的操作将失败,因为在第一次渲染过程之后,配置才具有属性
foo

<span data-bind="text:config.foo"><span>

在foreach绑定的模板中不需要像这样显式,因为模板是为每个实例呈现的。您只需预先声明可观察的array

这是承诺的一个用例。我不确定javascript是否支持跨浏览器,但它似乎与其他异步语言类似。

如果使用jquery,它会变得更简单:

$.ajax({
  url: "http://fiddle.jshell.net/favicon.png",
  beforeSend: function( xhr ) {
    xhr.overrideMimeType( "text/plain; charset=x-user-defined" );
  }
}).done(function( data ) {
  if(console && console.log ) {
    console.log( "Sample of data:", data.slice( 0, 100 ) );
  }
});

您不能像现在这样返回
config
。你就是不能。ajax调用是异步的,因此它将立即返回,您将返回一个空的
config
对象。使用异步ajax处理此问题的唯一方法是为异步代码设计。您可以使用promises,也可以从成功处理程序调用函数,并将现在完成的
config
对象传递给它。无论哪种方式,您都将在回调函数中获得完成的
config
对象

define("config", [], function () {
  var config = {};
  $.ajax({
    url: "api/Config",
    success: function (result) {
      for (pname in result) {
        config[pname] = ko.observable(result[pname]);
      }
      // call your function now and pass it the completed config object
      callMyFunction(config);
    }

  });
});
您可能认为这是一种解决方法,但这是异步操作的编码方式。

我建议使用in-durandal来处理此问题

define(['plugins/http', 'services/logger'], function (http, logger) {
    "use strict";
    var getConfig = function () {
        return http.get("api/Config").then(function (data) {
            return data;
        });
    };
    return {
        getConfig: getConfig
    };
});
这应该是一个行动的问题

define(['durandal/system', 'durandal/app', 'durandal/viewLocator', 'config'], 
       function (system, app, viewLocator, config) {
  //>>excludeStart("build", true);
  system.debug(true);
  //>>excludeEnd("build");

  app.title = 'Jumbo File Transfer';

  app.configurePlugins({
    router: true,
    dialog: true,
    widget: true,
    http: true
  });

  config.getConfig().then(app.start()).then(function () {
    //Replace 'viewmodels' in the moduleId with 'views' to locate the view.
    //Look for partial views in a 'views' folder in the root.
    viewLocator.useConvention();

    //Show the app by setting the root view model for our app with a transition.
    app.setRoot('viewmodels/shell', 'entrance');
  });
});
编辑
我认为您只需要在ajax调用的
done
函数中添加您所拥有的内容,就可以得到您所需要的

define('configFactory', ['plugins/http'], function (http) {
  "use strict";
  var getConfig = function () { 
    return http.get("api/Config").then(function (data) { 
        var config = {};

        for (pname in data){
             config[pname] = ko.observable(data[pname]);
        }

        return config; 
    });
  };
  return { getConfig: getConfig };
});

有一个更简单的答案。
如果您使您的
config
可见,则可以稍后更新:

define("config", [], function () {
  var config = ko.observable({}); // 1. Make config observable
  $.ajax({
    url: "api/Config",
    success: function (result) {
      //for (pname in result) {
      //    config[pname] = ko.observable(result[pname]);
      //}
      config(result); // 2. Replace the existing config object
    },
  });
  return config;
});
因此,您的html将更新为:

<img data-bind="attr: { src: config().Logo }" />


这对于在aync操作完成时安排其他行为很好,但不会阻止调用上下文。我可以将main.js的其余部分放在done处理程序中,该处理程序确实解决了特定的问题,但它没有教我如何同步异步进程。我现在可以从您的编辑中看到。好吧,我怀疑你能摆脱它,因为即使你将config.Logo设置为一个包含该逻辑的包装器对象,它在执行.done/success之前是没有定义的。。这让我想到了一个问题:为什么这个观点是超前的?如果可能的话,我会改变这一点。在收到ajax响应之前,您不应该构建或呈现视图吗?在Backbone.js中,在ajax获取集合之前,不会构造视图。我想,knockout.js也是如此。这几乎可以奏效。请参阅我的问题编辑。对您的输入非常感兴趣。哪一位不起作用?你是说一小时前的编辑吗?不是,是最新的。对不起,我已经偏离了方向。你知道吗,我差点就这么做了。可能应该有,就像你说的更简单。但有时它是关于旅程的。这不起作用,因为config()在绑定时没有Logo属性。