在返回之前等待异步完成

时间:2014-04-09 01:53:31

标签: javascript asp.net-mvc knockout.js durandal amd

此代码返回一个配置对象,最终用knockout observables填充它。

define("config", [], function () {
  var config = {};
  $.ajax({
    url: "api/Config",
    success: function (result) {
      for (pname in result)
        config[pname] = ko.observable(result[pname]);
    },
  });
  return 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 : '' }" />

但我不想用垃圾污染我的观点,所以我想知道如何在从工厂方法返回之前等待异步操作。

到目前为止,我试图应用收到的建议。因为result现在在范围内并且在调用define时可以使用,所以我只是直接将它传递给define。

$.ajax({
    url: "api/Config",
    success: function (result) {
      define("config", [], result);
    }
});

//the following code should not execute until config has been defined.

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

这样运行但是留下了如何使应用程序的其余部分等到发生这种情况的问题。所以我将其余的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');
      });
    });
  }
});

这实际上以正确的顺序执行 - 我逐步完成了它。但该应用程序无法启动。如果我不得不猜测为什么,我会说要将this定义为全局背景。

Nathan在下面的回答并没有阻止Durandal正常启动,但是配置定义的行为并不完全正确。我需要将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块,就像这样。

var foo;

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

//code that needs result

但是,引入瓶颈很少是一个好主意。

以下是基本问题:以下内容将失败,因为在第一次渲染过程之后,配置没有属性foo

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

我们可以在main中同步加载配置,但更好的答案是推迟绑定,直到foo可用。

<!-- ko if: config.foo -->
<span data-bind="text:config.foo"><span>
...
<!-- /ko -->

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

4 个答案:

答案 0 :(得分:1)

这是承诺的用例。我不确定javascript的跨浏览器支持,但它似乎与其他异步语言一样。 Full raw javascript example on this page

如果你使用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 ) );
  }
});

JQuery Docs about Ajax Promises

答案 1 :(得分:1)

我建议在durandal中使用http plugin来处理这个问题。

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

答案 2 :(得分:0)

您不能像往常一样返回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);
    }

  });
});

您可能认为这是一种解决方法,但它是您编写异步操作的方式。

答案 3 :(得分:0)

有一个更简单的答案 如果您使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 }" />