在揭示模块模式中返回jQuery承诺

时间:2015-08-20 22:33:07

标签: javascript jquery cookies jquery-deferred revealing-module-pattern

我正在Revealing Module模式中编写一个自定义库来处理特定的cookie并尝试使用jQuery Promise作为cookie的回报#34; Getter"函数以保持调用函数的函数在最初设置之前更新函数,从而使函数保持同步。

见下文:



/**
 * Handles the state cookie for a browser.
 *
 * JS DEPENDENCIES:
 * - jQuery Cookie plugin
 *
 * DOM DEPENDENCIES:
 * - None
 *
 */
var myUtilities = myUtilities || {};

myUtilities.stateManager = (function() {
    var cookieName = 'us_state';

    /**
     * Find geolocation state / set cookie
     * The passed deferred object only gets set as resolved if the AJAX response has the resulting data we need. Otherwise it is rejected.
     *
     * @param  {Object} position Passed from 'navigator.geolocation.getCurrentPosition'. Contains browser's approximation of its current latitude+longitude.
     * @return {Object}          The promise resolution (resolve or reject). Resolved has a String of state abbreviation in lowecase. Rejected is empty.
     */
    function _getLocation(position) {
        var latitude  = position.coords.latitude,
            longitude = position.coords.longitude;

        /* TEST VALUES */
        /* CA coords */
        // latitude  = '37.7833';
        // longitude = '-122.4167';
        /* AZ coords */
        // latitude  = '33.45';
        // longitude = '-112.0667';

        // If this errors out due to CORS issue (or similar issue) of if the return value doesn't match then we set the promise to reject
        return $.ajax({
            url: 'https://maps.googleapis.com/maps/api/geocode/json?latlng=' + latitude + ',' + longitude,
            dataType: "json"
        });
    }

    /**
     * Defer for getCurrentPosition callback
     * Create an anonymous function to handle success; accepts a Position object as argument, and calls _getLocation() passing in the position object.
     * When AJAX promise is complete evalute the data to find the state abbreviation.
     * Reject a failed call for getCurrentPosition (user did not allow/timeout on browser's request to use geolocation)
     *
     * @var {Object} $df jQuery Deferred object
     * @return {Object} jQuery Promise
     */
    function _deferGetLocation() {
        var $df = $.Deferred();

        if ("geolocation" in navigator) {
            navigator.geolocation.getCurrentPosition(
                function(position) {
                    _getLocation(position)
                        .then(function(data) {
                            if (data.length !== 0) {
                                var result  = data.results[0],
                                    address = '',
                                    state   = '';

                                // A for-loop is used because the response changes based on the address that Google API returns (a single search into a specific part of the data Object is not always successful evne though the data may be in there)
                                for (var i = 0, len = result.address_components.length; i < len; i++) {
                                    address = result.address_components[i];

                                    if (address.types.indexOf('administrative_area_level_1') >= 0) {
                                        // By returning here we exit the loop as soon as we get a match, like a 'break'
                                        $df.resolve(address.short_name.toLowerCase());
                                        break;
                                    }
                                }
                            }
                        });
                    });
        } else {
            $df.reject();
        }

        return $df.promise();
    }

    /**
     * Either get the get cookie or set it now.
     * If the cookie exists we resolve the promise immediately, else wait for the geolocation to be resolved, set state cookie and resolve.
     *
     * @var {Object} $df         jQuery Deferred object
     * @var {String} stateString state, 2 character abbreviation format
     * @return {Object} Promise with a String for the callback (two-character value indicating which state the user is in)
     */
    function _getStateCookie(){
        var $df = $.Deferred();

        if ($.cookie(cookieName)) {
            $df.resolve($.cookie(cookieName));
        } else {
            _deferGetLocation()
                .then(function(state) {
                    $df.resolve(_setStateCookie(state));
                });
        }

        return $df.promise();
    }

    /**
     * Set the 'cookieName' cookie to a desired state, or default to 'co'
     *
     * @param {String} state The value of the cookie as a 2 character length state abbreviation
     * @param {Datetime} expirationDate Days until the cookie expires
     */
    function _setStateCookie (state, expirationDate){
        state          = ( typeof state == 'undefined' || !_isValidState(state) ) ? 'co' : state;
        expirationDate = ( typeof expirationDate == 'undefined' ) ? 365 : expirationDate;

        $.cookie(cookieName, state, { path: '/', expires: expirationDate });

        // Offer an event listener for this cookie
        $(document).trigger('state-utility.cookieChange');

        return state;
    }

    /**
     * Validates a given string against our predetermined "valid states" (AZ, CA, CA).
     * Returns  true if valid, false otherwise.
     * Case-sensitive, AZ == az -> false
     *
     * @param  {String}  state A value to be compared for valid state
     * @return {Boolean}       True if valid, false otherwise
     */
    function _isValidState(state) {
        return (state == 'az' || state == 'ca' || state == 'ca');
    }

    function _isCookieSet() {
        return ($.cookie(cookieName) && _isValidState($.cookie(cookieName)));
    }

    return {
        // Using a Promise so that multiple calls to _getStateCookie() are handled synchronously
        getStateCookie : function() {
            return _getStateCookie().then( function(state) { return state; });
        },
        setStateCookie : function(state, expirationDate) {
            return _setStateCookie(state, expirationDate);
        },
        updateStateElement : function(target) {
            return _updateStateElement(target);
        },
        isValidState : function(state) {
            return _isValidState(state);
        },
        isCookieSet : function() {
            return _isCookieSet();
        }
    };
})();
&#13;
<script src="https://raw.githubusercontent.com/carhartl/jquery-cookie/master/src/jquery.cookie.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
&#13;
&#13;
&#13;

尝试使用myUtilities.stateManager.getStateCookie()检索Cookie的值时出现问题。我希望这个调用返回一个两个字符的字符串,用于最近的适用状态。相反,我得到了Promise对象。

为什么返回Promise而不是字符串,为了返回所需的字符串需要更改什么?

感谢您的时间。

2 个答案:

答案 0 :(得分:1)

我担心你不能期望从javascript中的异步进程中获得同步结果。您所做的任何事情都不会将异步转换为同步。你可以期待的最好的(在可预见的未来的某一天)是使异步代码看起来更像同步的语法。

以下是一些建议......

_getLocation()中,我会:

  • 添加一个失败处理程序,将jQuery.ajax的错误报告规范化为一个原因。
function _getLocation(position) {
    var latitude  = position.coords.latitude,
        longitude = position.coords.longitude;

    return $.ajax({
        url: 'https://maps.googleapis.com/maps/api/geocode/json?latlng=' + latitude + ',' + longitude,
        dataType: "json"
    }).then(null, function(jqXHR, textStatus, errorThrown) {
        return errorThrown;
    });
}

_deferGetLocation()中,我会:

  • _deferGetLocation()清除显式的promise构造反模式。
  • 提供两份承诺拒绝承诺的理由。
  • 微微整理。
function _deferGetLocation() {
    var promise;
    if ("geolocation" in navigator) {
        navigator.geolocation.getCurrentPosition(function(position) {
            promise = _getLocation(position).then(function(data) {
                var result = data.results[0],
                    state;
                if (data.length !== 0) {
                    // A for-loop is used because the response changes based on the address that Google API returns (a single search into a specific part of the data Object is not always successful even though the data may be in there)
                    for (var i = 0, len = result.address_components.length; i < len; i++) {
                        if (result.address_components[i].types.indexOf('administrative_area_level_1') >= 0) {
                            state = result.address_components[i].short_name.toLowerCase();
                            break;
                        }
                    }
                }
                return state || $.Deferred().reject('geolocation failed').promise();
            });
        });
    return promise || $.Deferred().reject('browser does not support geolocation').promise();
}

在重命名的_getStateCookie()中,我会:

  • 重命名为_getStateCookieAsync(),以警告消费者该方法返回一个承诺。
  • _getStateCookie()清除显式的promise构造反模式并简化(大时间)。
function _getStateCookieAsync() {
    var state = $.cookie(cookieName);
    return (state) ? $.when(state) : _deferGetLocation().then(_setStateCookie);
}

在方法暴露的return语句中我会:

  • 只揭示必要的内容 - 没有义务公开每一种方法。
  • 按函数名称公开 - 不需要额外的函数包装器。
return {
    getStateCookieAsync : _getStateCookieAsync,
    setStateCookie : _setStateCookie, // will it ever be set from outside?
    // updateStateElement : _updateStateElement, // doesn't exist
    isValidState : _isValidState, // probably only of use internally
    isCookieSet : _isCookieSet
};

答案 1 :(得分:0)

只有通过附加.then()处理程序才能获得超出承诺的值,并且所有.then()处理程序都是异步执行的。

所以,这段代码根本不起作用:

getStateCookie : function() {
        return _getStateCookie().then( function(state) { return state; });
},

这只会返回一个价值为state的承诺。请注意,.then()处理程序在此处不会为您的代码添加任何内容。

您的结果是异步的。你不能改变它。呼叫者必须将其作为异步处理。这意味着调用者将通过回调函数获得结果。该回调可以是普通回调或承诺回调。既然你已经在使用promises,那么最简单的方法就是返回将包含值的promise并让调用者在其上放置自己的.then()处理程序,这样它就可以将值传递给它自己的回调函数。

我建议:

getStateCookie : function() {
    return _getStateCookie();
},

然后,呼叫者以这种方式使用它:

myUtilities.stateManager.getStateCookie().then(function(state) {
    // callers code goes here to use state
});