我正在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;
尝试使用myUtilities.stateManager.getStateCookie()
检索Cookie的值时出现问题。我希望这个调用返回一个两个字符的字符串,用于最近的适用状态。相反,我得到了Promise对象。
为什么返回Promise而不是字符串,为了返回所需的字符串需要更改什么?
感谢您的时间。
答案 0 :(得分:1)
我担心你不能期望从javascript中的异步进程中获得同步结果。您所做的任何事情都不会将异步转换为同步。你可以期待的最好的(在可预见的未来的某一天)是使异步代码看起来更像同步的语法。
以下是一些建议......
在_getLocation()
中,我会:
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
});