문제

I'm trying to work out the correct pattern to use for a Singleton that initializes with an AJAX call. This is a simplified example of what I have working already.

It works as expected, but I have a feeling this is not the "correct" way and that there's some way to hook the initialiser's callback into the success call of the currently running ajax request, and I'm concerned that there could be a race condition with the array approach. Am I on the right track here?

var singletonObj;
$.Class("SingletonTest", {
  init: function(onSuccess) {
    if (singletonObj) {
      if (singletonObj.ajaxLoaded) {
        onSuccess(singletonObj);
      }
      else {
        singletonObj.callbacks.push(onSuccess);
      }
    }
    else {
      singletonObj = this;
      singletonObj.callbacks = new Array(onSuccess);
      singletonObj.count=0;

      $.ajax({
        url: "/path/to/json/config",
        method: "GET",
        success: function (res) {
          singletonObj.data = res.meta_data;
          singletonObj.ajaxLoaded = true

          singletonObj.callbacks.forEach(function(callback) {
            callback(singletonObj);
          });
        }
      });
    }
  },
  counter: function() {
    return this.count++;
  }
});

new SingletonTest( function(obj) { console.log("First call: "+obj.counter() ) });
new SingletonTest( function(obj) { console.log("Second call: "+obj.counter() ) });
new SingletonTest( function(obj) { console.log("Third call: "+obj.counter() ) });

Or is there a simpler way to do this? What concept am I missing here that would make life easier?

도움이 되었습니까?

해결책

Since you're looking the "correct" way here are some general notes:

  1. You don't need a class for a singleton (JavaScript isn't Java). Just make it a global obj or better yet a function.

  2. $.Deferred is your friend. $.ajax returns a promise.

Here is a functional pattern for singletons:

// creates a lazy singleton from a factory function
function singleton(factory) {
    var deferred;
    return function() {
        return deferred || (deferred = factory());
    };
}

// declare your specific singleton
var SingletonTest = singleton(function() {
    return $.ajax({
        url: "/path/to/json/config",
        method: "GET"
    }).pipe(function(obj) {
        // pipe lets you modify the result
        // here we add the counter method
        var count = 0;
        obj.counter = function() {
            return count++;
        };
        return obj;
    });
});

// use it
SingletonTest().then(function(obj) {
    console.log("First: "+obj.counter());
});

SingletonTest().then(function(obj) {
    console.log("Second: "+obj.counter());
});

If you find yourself using this pattern a lot, there is a JMVC plugin (disclosure: I'm the primary author) that implements a functional form of dependency injection.

If you were to use Inject, it would look like this:

var cache = Inject.cache();
var Injector = Inject(
    cache.def('SingletonTest',function() {
        return $.ajax({
            url: "/path/to/json/config",
            method: "GET"
        }).pipe(function(obj) {
            var count = 0;
            obj.counter = function() {
                return count++;
            };
            return obj;
        });
    })
);

var logCount = Injector('SingletonTest',function(obj,message) {
    console.log(message+obj.counter());
});

logCount("First:");
logCount("Second:"); 

For large projects with a lot of singletons or just deferred data, injecting scales better than global variables.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top