Search code examples
javascriptfingerprintjs2

run a callback after an instance of a class-style object in Javascript


I'm taking on someone else's code and not sure if I'm going about doing something the right way.

There's a Tracker 'class' (basically just an object of functions, including an 'initializer').

The class will make a call to a third party library.

A callback should happen after the results are returned.

Here's what I'm trying to do:

var tracker = Tracking.initialize({
        prod: 'OurProduct',
        requiredLayoutKeys: ["scr"],
        requiredInteractiveKeys: ["name", "action"],
        maxQueueSize: 5,
        timeToSend: 5000,
    });

// this code should happen in the callback once tracker is initialized:
InitSessionInfo(
    {sid: tracker.__sid, product: tracker.prod},
    function (returnObj) {
        setSessionIP(tracker.getIP(),function(returnObj){
            OurApp.auth(function (user) {
                OurApp.initialize(user);
            });
        });
    });

Here is what the Tracking object looks like in a separate file:

var Tracking = {
  __required_layout_keys: null,
  __required_interactive_keys: null,
  __maxQueueSize: null,
  __timeToSend: null,
  __initialized: false,

  initialize: function(config){
    Tracking.__initialized = true;
    Tracking.__required_interactive_keys = config.requiredInteractiveKeys;
    Tracking.__required_layout_keys = config.requiredLayoutKeys;
    Tracking.__maxQueueSize = config.maxQueueSize;
    Tracking.__timeToSend = config.timeToSend;
    Tracking.__bid = '';
    Tracking.__sid = '';
    Tracking.__ua = '';

    // get user data from FingerPrintJs2. 
    // The callback needs to happen after these results are returned: 
    new Fingerprint2().get(function(result, components){
        var ua = components.find(function(item){
            return item.key === "user_agent";
        });
        Tracking.__bid = result;
        Tracking.__sid = result + "." + Date.now();
        Tracking.__ua = ua.value;
    });
  },
 // there are other functions here that return ip, etc.
}

Unfortunately 'tracker' always comes back as undefined.

update:

when sticking a debugger here:

var tracker = Tracking.initialize({
    prod: 'OurProduct',
    requiredLayoutKeys: ["scr"],
    requiredInteractiveKeys: ["name", "action"],
    maxQueueSize: 5,
    timeToSend: 5000,
});

debugger;

the value of tracker is 'undefined' and the value of Tracking does not yet have its 'sid' value set.

Updates in Tracking:

var Tracking = {
 ...
 initialize: function(){
 ...
 return new Fingerprint2().get(function(result, components){
        var ua = components.find(function(item){
            return item.key === "user_agent";
        });
        Tracking.__bid = result;
        Tracking.__sid = result + "." + Date.now();
        Tracking.__ua = ua.value;
        return Tracking;
    });


 }

Solution

  • So, fingerprintjs2 is (deep down in a series of internal function calls) returning a setTimeout, which is asynchronous by nature, but unfortunately you cannot chain off of by default. You can see, by looking at code inside the setTimeout, that it is invoking the function that you pass in to the get() function when the timeout has finished, so you can leverage that with promises to make sure things happen when they should.

    What you have to do is wrap the new Fingerprint2().get() call with a promise, and then resolve that promise inside the "done" function (the one that gets called inside the setTimeout) that you pass into the get function.

    Then, you can return the promise from the initialize function in your Tracking object so that you can chain off of it and perform actions after the data has been successfully initialized/updated.

    ES6 Promises & Chaining Docs

    Working DEMO

    ES6 Version using Promise object

    var Tracking = {
      __required_layout_keys: null,
      __required_interactive_keys: null,
      __maxQueueSize: null,
      __timeToSend: null,
      __initialized: false,
      initialize: function(config){
        Tracking.__initialized = true;
        Tracking.__required_interactive_keys = config.requiredInteractiveKeys;
        Tracking.__required_layout_keys = config.requiredLayoutKeys;
        Tracking.__maxQueueSize = config.maxQueueSize;
        Tracking.__timeToSend = config.timeToSend;
        Tracking.__bid = '';
        Tracking.__sid = '';
        Tracking.__ua = '';
        // get user data from FingerPrintJs2. 
        // The callback needs to happen after these results are returned: 
        // return the promise so we can chain
        return new Promise(function (resolve, reject) {
            new Fingerprint2().get(function(result, components){
              var ua = components.find(function(item){
                return item.key === "user_agent";
              });
              Tracking.__bid = result;
              Tracking.__sid = result + "." + Date.now();
              Tracking.__ua = ua.value;
              resolve(Tracking); // resolve promise with updated Tracking obj
            });
        });
      },
      // there are other functions here that return ip, etc.
    };
    
    var tracker = null;
    Tracking.initialize({
      prod: 'OurProduct',
      requiredLayoutKeys: ["scr"],
      requiredInteractiveKeys: ["name", "action"],
      maxQueueSize: 5,
      timeToSend: 5000,
    }).then(function (updatedTracker) {
        tracker = updatedTracker;
        console.log(tracker);
    });
    

    ES5 Compatible Version using $.Deferred()

    jQuery Deferred Promise Docs

    A small bit of the syntax changes here, but nothing drastic - and the idea is the exact same.

    var Tracking = {
      __required_layout_keys: null,
      __required_interactive_keys: null,
      __maxQueueSize: null,
      __timeToSend: null,
      __initialized: false,
      initialize: function(config){
        Tracking.__initialized = true;
        Tracking.__required_interactive_keys = config.requiredInteractiveKeys;
        Tracking.__required_layout_keys = config.requiredLayoutKeys;
        Tracking.__maxQueueSize = config.maxQueueSize;
        Tracking.__timeToSend = config.timeToSend;
        Tracking.__bid = '';
        Tracking.__sid = '';
        Tracking.__ua = '';
        // get user data from FingerPrintJs2. 
        // The callback needs to happen after these results are returned: 
        var deferred = $.Deferred(); // create deferred object
        new Fingerprint2().get(function(result, components){
              var ua = components.find(function(item){
                return item.key === "user_agent";
              });
              Tracking.__bid = result;
              Tracking.__sid = result + "." + Date.now();
              Tracking.__ua = ua.value;
              deferred.resolve(Tracking); // resolve deferred object with updated Tracking obj
            });
        return deferred.promise(); // return deferred promise that we can chain off of
      },
      // there are other functions here that return ip, etc.
    };
    
    var tracker = null;
    // jQuery deferreds work with $.when(promise).then() syntax
    $.when(Tracking.initialize({
      prod: 'OurProduct',
      requiredLayoutKeys: ["scr"],
      requiredInteractiveKeys: ["name", "action"],
      maxQueueSize: 5,
      timeToSend: 5000,
    })).then(function (updatedTracker) {
        tracker = updatedTracker;
        console.log(tracker);
    });