javascript - 在揭示模块模式中返回 jQuery Promise

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

我正在 Revealing Module 模式中编写一个自定义库来处理特定的 cookie,并尝试使用 jQuery Promise 作为 cookie 的“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();
        }
    };
})();
<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>

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

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

感谢您的宝贵时间。

最佳答案

恐怕您永远无法期望从 JavaScript 中的异步过程中派生出同步结果。你所做的任何事情都不会将异步转换为同步。您可以期望的最好的结果(在可预见的 future 的某一天)是使异步代码看起来更像同步的语法。

这里有一些建议...

_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 构造反模式。
  • 提供在两种情况下拒绝 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(),以警告消费者该方法返回一个 promise 。
  • _getStateCookie() 中清除显式的 Promise 构造反模式并进行简化(大量)。
function _getStateCookieAsync() {
    var state = $.cookie(cookieName);
    return (state) ? $.when(state) : _deferGetLocation().then(_setStateCookie);
}

在方法公开的返回语句中,我会:

  • 仅公开必要的内容 - 没有义务公开每个方法。
  • 通过函数名称公开 - 无需额外的函数包装器。
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
};

关于javascript - 在揭示模块模式中返回 jQuery Promise,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32129529/

相关文章:

javascript - 为什么我的替换方法会抛出错误?

javascript - 使用 Immutable JS 和 Redux - Action 未定义

Jquery,通过ID选择icefaces元素

jquery - CSS 转换不适用于 jQuery .addClass 和 .removeClass

php - 使用 cookie 在 PHP 中创建 "remember me"系统的安全方法是什么?

javascript - 如何向用户显示警报框,除非他们单击特定按钮

javascript - 整数/十进制用户输入 : how to serialize/store the value

javascript - 如何在html Canvas 中制作平行线

python - Django:Cookie 设置为 30 秒后过期实际上是 30 分钟后过期?

authentication - Nuxt auth SSR cookie 仅在关闭浏览器和加载页面时出现问题