开发者

wrapping jquery ajax in function with return

I am trying to make some of my javascript a bit more testable, as part of this I am wrapping up certain functionality in classes so I can mock it out in my tests.

Anyway I am trying to wrap up my ajax, so as far as my app would be concerned it is asking a service for an object. Internally it would make an ajax request and then do something with the data and then return it.

So would something like the example below be possible? (I am on the move at the moment so cant try it for myself)

function SomeAjaxService(webServiceUrl)
{
    this.getSomeModel = function(someUniqueId){
        var ajaxOptions = {
            url: webServiceUrl,
            data: {id : someUniqueId},
            Success: function(d开发者_C百科ata) { return new SomeModel(data); }
        };
        $.ajax(ajaxOptions);
    };
}

var ajaxService = new SomeAjaxService("http://someurl");
var myModel = ajaxService.getSomeModel(1);

As ajax is asynchronous by nature, I think you can set it to be synchronous but I just wanted to check what the options were...


Setting it to synchronous is a bad idea as you will lock javascript (even the browser) until the request returns. Your code will not work because the success function is called from a different context to your direct getSomeModel method call. The best approach would be for you to call getSomeModel and pass it a callback function that will be executed when the model is received.

function SomeAjaxService(webServiceUrl)
{
    this.getSomeModel = function(someUniqueId, callback){
        var ajaxOptions = {
            url: webServiceUrl,
            data: {id : someUniqueId},
            // calls the callback you passed
            Success: function(data) { callback(new SomeModel(data)); }
        };
        // this is asynchronous and doesn't return anything
        $.ajax(ajaxOptions);
    };
}

var ajaxService = new SomeAjaxService("http://someurl");
// your callback down here
var myModel;
ajaxService.getSomeModel(1, function(model) {
    myModel = model;
    // do something with model
});


I too had come across this problem and clearly the callback approach works, with the jqXHR object that is returned from the $.ajax() call implementing the Promise interface and thus I can make deferred calls to the done method (or even change the functionality of the done method at a later point in time. Here is one working sample:

//some separate js file to be included via a <script src=...></script>
//suppose it's named simpleWrappers.js
function wrapAjax(dataURL,postData) {
  postData = (typeof postData === 'string') ? postData : "";
  respObj = $.ajax({
      url: dataURL
    , data: postData
    , dataType: 'json'
    , type: 'POST'
  });
  return respObj
}

Now let's include the simpleWrappers.js file in some page with its own doc ready function

//...
<script src="js/simpleWrappers.js"></script>
//...
<script>
  $(document).ready(function() {
    //...
    myData = wrapAjax("scripts/returnCoolData.php?fname=fetchSuperCoolData");
    //now attach a done handler!
    myData.done(function(data,statusText,jqXHR) {
      //we'll do some trivial logging
      console.log("the status of the request was: " + statusText);
      //note, my data comes back in JSON, so I JSON.stringify() the data
      console.log("data returned is: " + JSON.stringify(data));
      //Note: don't expect responseText to be available, as in
      //console.log("responseText was: " + myData.responseText)
      //may well return "responseText was: undefined, see above answers
      //to grasp the calling context issue
    }); 
  }
</script>


You can set it to be synchronous by adding the async: false option to your call, but your function still won't return anything, because the return statement in the success callback is returning from the callback, not the main function.

If I'm reading you right, then (changes flagged with *):

function SomeAjaxService(webServiceUrl)
{
    this.getSomeModel = function(someUniqueId){
        var rv;                                                   // *
        var ajaxOptions = {
            url: webServiceUrl,
            async: false,                                         // *
            data: {id : someUniqueId},
            Success: function(data) { rv = new SomeModel(data); } // *
        };
        $.ajax(ajaxOptions);
        return rv;                                                // *
    };
}

But rather than making your ajax calls synchronous for testing, which could well have side-effects and make the tests invalid, I'd strongly recommend making your test framework asynchronous instead. An asynchronous test framework can perform synchronous tests; a synchronous test framework can't perform asynchronous tests. So the framework should be asynchronous...

I'd also strongly advise against rolling your own test framework. There are a bunch of ones you can choose from without building your own.


Update: ...but if you really want to build your own, here's a very simple example of what I mean by it not being hard to make the framework asynchronous (live copy):

jQuery(function($) {

  $("#theButton").click(function() {
    var tests, testIndex, nesting;

    tests = [
      function(cb) { display("I'm test1: sync");  cb(1); },
      function(cb) { display("I'm test2: async"); setTimeout(function() {
        cb(2);
      }, 0); },
      function(cb) { display("I'm test3: sync");  cb(3); },
      function(cb) { display("I'm test4: async"); setTimeout(function() {
        cb(4);
      }, 0); }
    ];

    nesting = 0;
    testIndex = 0;
    doTest();

    function doTest() {
      if (testIndex >= tests.length) {
        return true; // Done
      }
      ++nesting;
      oneTest(tests[testIndex++]);
      --nesting;
      return false;
    }

    function testDone(result) {
      display("Result: " + result);
      if (nesting > 0) {
        // Completion was called synchronously; schedule next
        // test asynchronously to avoid recursing too deeply.
        // You could use a higher number than 0 to allow *some*
        // recursion for efficiency but avoid letting too much
        // occur.
        display("r"); // Just for demonstrating that it happens
        setTimeout(doTest, 0);
      }
      else {
        // We were already called asynchronously, start next
        doTest();
      }
    }

    function oneTest(test) {
      test(testDone);
    }

  });

  function display(msg) {
    $("<p>").html(msg).appendTo(document.body);
  }

});


ajax functions will be called but you won't get instance of someModel on success as far as getSomeModel does not return anything. Solution is to use trigger function.

function SomeAjaxService(webServiceUrl)
{
    this.getSomeModel = function(someUniqueId){
        var ajaxOptions = {
            url: webServiceUrl,
            data: {id : someUniqueId},
            Success: function(data) { model = new SomeModel(data); $.trigger("SomeModelInit", model) }
        };
        $.ajax(ajaxOptions);
    };
}

var ajaxService = new SomeAjaxService("http://someurl");
$.bind("SomeModelInit", function (model) {
//process
})

another way to test html/javascript/css is selenium

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜