我为Node.js编写了以下代码以连接到CMS的API。 API必须使用用户名和密码登录一次,然后继续使用访问令牌进行请求。但是,访问令牌的有效时间有限-之后,API必须再次登录并接收新令牌。
当我向API发出请求时,我总是尝试使用当前令牌-如果它不再有效,我将收到403并触发新的登录,然后再次执行初始请求。
这里的问题是,如果多个请求同时尝试访问API,它们都会触发登录-这导致CMS中出现多个API会话,这是一件坏事。我以为我已经用Promises处理了这种情况,但是显然我的代码没有我想象的那样。
var api = {
url: 'servername',
user: 'user',
password: 'password',
token: null
};
var login_in_progress = true;
var loginPromise;
function loginApi() {
login_in_progress = true;
return new Promise(function (resolve, reject) {
request({
uri: api.url + api.user + ',' + api.password,
method: 'GET',
json: true
}, function (err, res, data) {
if (!err && res.statusCode === 200) {
api.token = data.token;
login_in_progress = false;
resolve(data);
} else {
login_in_progress = false;
reject(err);
}
});
});
}
var getContent = function (query) {
// Currently a login is running - wait till it's finished and then execute get
// at least that was the idea - but does not seem to work
if (login_in_progress && loginPromise) {
return new Promise(function (resolve, reject) {
loginPromise
.then(function () {
getContent(query)
.then(function (data) {
resolve(data);
})
.catch(function (err) {
reject(err);
});
})
.catch(function (err) {
reject(err);
});
});
} else {
// Do the actual request
// case 403 Api is logged out => reLogin
loginPromise = loginApi();
loginPromise
.then(function () {
getContent(query)
.then(function (data) {
resolve(data);
})
.catch(function (err) {
reject(err);
});
})
.catch(function (err) {
reject(err)
});
}
}
显然,没有检查当前是否未运行登录,并且getContent函数始终会运行到else语句中。我什至不确定我的想法是否可以检查正在运行的登录请求。
答案 0 :(得分:1)
我相信类似的方法可以工作(nb:这确实是“伪代码”,而不是可运行的代码):
let authPromise = undefined;
function getContent(query) {
if (!authPromise) {
// no login pending - start one, then restart the whole function
authPromise = login();
return authPromise.then(() => getContent(query));
} else {
// login pending - wait for it, then make the real call
// if the login promise already resolved, it won't wait
return authPromise.then(() => makeAjaxRequest(query)).then((response) => {
if (response.status === 403) {
// session expired - remove the state, and restart the whole function
authPromise = undefined;
return getContent(query);
} else {
// session still valid, return the API response
return response;
}
});
}
}
唯一的状态是是否存在Promise
。
答案 1 :(得分:1)
在您的代码中有几件事可以纠正或简化。我在这里用变量testlogged
模拟登录和注销,该变量替换了原始代码中的403响应,并用超时替换了登录延迟,只是为了演示如何使用最少的代码来完成该操作。
注意:
getContent
中嵌套嵌套的Promise。尚不清楚该函数是否使用另一个函数来获取返回承诺的内容,或者是否需要本身返回承诺。这不是同一回事。对于第二种情况,您可以参见示例2。getContent
返回的内容保持一致,并了解要成为承诺的内容。您可以将回调传递给getContent
(示例1),也可以在其上使用then
(示例2)。getContent
很简单,但是如果它返回一个Promise我不愿意,因为它带来了额外的复杂性,从而迫使在函数本身内部对其使用then
。getContent
从一开始返回一个新的Promise(包装所有代码),则更加清楚示例1
var api = {
url: 'servername',
user: 'user',
password: 'password',
token: null
};
var testlogged = false;
var login_in_progress = true;
var loginPromise;
function loginApi() {
login_in_progress = true;
return new Promise(function (resolve, reject) {
setTimeout(function(){
console.log('login OK');
login_in_progress = false;
testlogged = true;
resolve();
}, 10);
});
}
function getContent(query, callback) {
// Currently a login is running - wait till it's finished and then execute get
if (login_in_progress && loginPromise) {
console.log('login in progress, waiting before getting content.. query: ' + query);
loginPromise
.then(function () {
callback('got content OK (1) query: ' + query);
})
.catch(function (err) {
console.error('error: ', err);
});
} else {
// Do the actual request
if (!testlogged) {
// case 403 Api is logged out => reLogin
console.log('starting login.. query: ' + query);
loginPromise = loginApi();
//restarting function to put query in queue, saves code, but you could also do directly loginPromise.then(function () {...
getContent(query, callback);
} else {
console.log('already logged');
callback('got content OK (2) query: ' + query);
}
}
}
var theCallback = function(data){
console.log(data);
};
//doing first attempt while not logged
getContent('test', theCallback);
//trying with a concurrent attempt while still logging
getContent('test2', theCallback);
//simulating attempt while still being logged then after logout (waiting first that the 2 precendent attempts are finished)
setTimeout(function(){
getContent('test3', theCallback);
}, 100);
setTimeout(function(){
console.log('simulate log out');
testlogged = false;
getContent('test4', theCallback);
}, 100);
示例2
var api = {
url: 'servername',
user: 'user',
password: 'password',
token: null
};
var testlogged = false;
var login_in_progress = true;
var loginPromise;
function loginApi() {
login_in_progress = true;
return new Promise(function (resolve, reject) {
setTimeout(function(){
console.log('login OK');
login_in_progress = false;
testlogged = true;
resolve();
}, 10);
});
}
var getContent = function (query) {
return new Promise(function (resolve, reject) {
// Currently a login is running - wait till it's finished and then execute get
if (login_in_progress && loginPromise) {
console.log('login in progress, waiting before getting content.. query: ' + query);
loginPromise
.then(function () {
resolve('got content OK (1) query: ' + query);
})
.catch(function (err) {
reject(err);
});
} else {
// Do the actual request
if (!testlogged) {
// case 403 Api is logged out => reLogin
console.log('starting login.. query: ' + query);
loginPromise = loginApi();
loginPromise
.then(function () {
resolve('got content OK (1) query: ' + query);
})
.catch(function (err) {
reject(err);
});
} else {
console.log('already logged');
resolve('got content OK (2) query: ' + query);
}
}
});
}
var theCallback = function(data){
console.log(data);
};
//doing first attempt while not logged
getContent('test').then(theCallback);
//trying with a concurrent attempt while still logging
getContent('test2').then(theCallback);
//simulating attempt while still being logged then after logout (waiting first that the 2 precendent attempts are finished)
setTimeout(function(){
getContent('test3').then(theCallback);
}, 100);
setTimeout(function(){
console.log('simulate log out');
testlogged = false;
getContent('test4').then(theCallback);
}, 150);
答案 2 :(得分:0)
因此,虽然答案可以帮助我更多地思考问题,但实际问题却与众不同:
我只在login_inprogress
启动时检查getContent
,而不是在403返回时检查。
同时启动请求时,这会导致多次API登录。
解决方案实际上非常简单:
// case 403 Api is logged out => reLogin
// check again if login is running NOW, only if not, start one
if(!login_in_progress){
loginPromise = loginApi();
}
// Always attach to the loginPromise to redo the initial getContent
loginPromise
.then(function () {
getContent(query)
.then(function (data) {
resolve(data);
})
.catch(function (err) {
reject(err);
});
})
.catch(function (err) {
reject(err)
});