简体   繁体   中英

Is it possible to initialize Google Maps asynchronously without creating a global callback function?

I am trying to write a re-usable drop-in Module to load Google Maps asynchronously and return a promise.

Here is the code I came up with, using AngularJS .

However, there is a re-usability drawback of creating a global callback function "behind the scene". Which is a side effect that may result in bugs, if the same namespace happens to be used by any other library.

My questions - is there any way to achieve this effect without creating global variables.

Below is the code that creates that "evil" global callback:

// Google async initializer needs global function, so we use $window
angular.module('GoogleMapsInitializer')
    .factory('Initializer', function($window, $q){

        // maps loader deferred object
        var mapsDefer = $q.defer();

        // Google's url for async maps initialization accepting callback function
        var asyncUrl = 'https://maps.googleapis.com/maps/api/js?callback=';

        // async loader
        var asyncLoad = function(asyncUrl, callbackName) {
          var script = document.createElement('script');
          //script.type = 'text/javascript';
          script.src = asyncUrl + callbackName;
          document.body.appendChild(script);
        };

// Here is the bad guy:

        // callback function - resolving promise after maps successfully loaded
        $window.googleMapsInitialized = function () {
            mapsDefer.resolve();
        };

        // loading google maps
        asyncLoad(asyncUrl, 'googleMapsInitialized');

        return {

            // usage: Initializer.mapsInitialized.then(callback)
            mapsInitialized : mapsDefer.promise
        };
    })

The script loading technique for getting data from a server on a cross-origin domain that you are referring to is JSONP. You can read more about it here . Somewhat by definition, JSONP can only work by calling a globally scoped function.

So, in direct answer to your question: no you can't use JSONP cross-origin techniques without the global function - that's just how the mechanism works. A script is executed in the global namespace and it has to call a function it can reach from the global namespace. Even jQuery and YUI do something like this for implementing JSONP.

And, since you are using Angular, it already has JSONP functionality built into it. See the doc here so you don't have to create your own mechanism for doing this.


But, that said, if you were making your own, you can make it much less likely that your global function will collide with anyone else's code or even with another instance of your library by taking some precautions to make the global names you create more random.


Here's an example of how you can make the odds of any sort of naming collision very small. This uses three techniques:

  1. Use some leading underscores on your prefix.
  2. Add a random sequence of digits to the function name.
  3. Add a time stamp to the function name.
  4. Remove the global after it is used.

Here is your code with those aspects implemented.

// Google async initializer needs global function, so we use $window
angular.module('GoogleMapsInitializer')
    .factory('Initializer', function($window, $q){

        // maps loader deferred object
        var mapsDefer = $q.defer();

        // Google's url for async maps initialization accepting callback function
        var asyncUrl = 'https://maps.googleapis.com/maps/api/js?callback=';

        // async loader
        var asyncLoad = function(asyncUrl, callbackName) {
          var script = document.createElement('script');
          //script.type = 'text/javascript';
          script.src = asyncUrl + callbackName;
          document.body.appendChild(script);
        };

        // generate a unique function name
        // includes prefix, current time and random number
        var fname = "__googleMapsInitialized_" + 
            (new Date().getTime()) + "_" + 
            (Math.random() + "").replace(".", "");

        // callback function - resolving promise after maps successfully loaded
        $window[fname] = function () {
            mapsDefer.resolve();
            // remove the global now that we're done with it
            delete $window[fname];
        };
        // loading google maps
        asyncLoad(asyncUrl, fname);

        return {

            // usage: Initializer.mapsInitialized.then(callback)
            mapsInitialized : mapsDefer.promise
        };
    })

Demo of the unique function name generator: http://jsfiddle.net/jfriend00/oms7vc6o/

PS I don't know Angular myself, but it appears that Angular already knows how to make JSONP calls all by itself so you don't have to make your own solution here. See this Angular doc page and this other question and this article for details.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM