我目前正在为网站实现客户端令牌刷新脚本,当需要刷新访问令牌时遇到了一个小问题。
对于某些页面,客户端从服务器获取多个(假设是两个)文档(这两个文档都需要通过授权请求获取,即使用访问令牌),因此当客户端访问令牌过期时,会调用此同时获取两次令牌刷新操作,客户端保存了最后一次结束的刷新操作的最终访问令牌。这是资源的浪费,因为第一个访问令牌从未被使用,甚至从未保存到客户端。
为了解决这个问题,我做了刷新令牌创建临时
localStorage
条目的过程:
async function getAccessToken(auth) {
// Check if the current token is valid, and return it if so
...
// Case when token needs to be refreshed
localStorage.setItem("TOKEN_PENDING", "TOKEN_PENDING");
// Refresh the token
...
const res = await fetch(...);
...
// Clean up after refresh
localStorage.removeItem("TOKEN_PENDING");
// Return the new token
...
}
显然,我现在需要检查在刷新令牌之前是否已创建此条目:
const isTokenPending = () => localStorage.getItem("TOKEN_PENDING") != null;
async function getAccessToken(auth) {
// Check if the current token is valid, and return it if so
...
// Case when token needs to be refreshed
localStorage.setItem("TOKEN_PENDING", "TOKEN_PENDING");
// Check if the token is currently being refreshed in a parallel instance of the function
if (isTokenPending()) {
while (isTokenPending()) {
await new Promise((resolve) => setTimeout(resolve, 0));
}
console.info("Token finished refreshing");
// Recursively call the function, which will now produce the new valid token
return getAccessToken(auth);
}
localStorage.setItem("TOKEN_PENDING", "TOKEN_PENDING");
// Refresh the token
...
const res = await fetch(...);
...
// Clean up after refresh
localStorage.removeItem("TOKEN_PENDING");
// Return the new token
}
有问题的部分是当函数检测到临时条目存在时,并等待直到令牌在函数的其他实例中刷新。目前,我已经尝试了
while
循环中看到的方法:
while (isTokenPending()) {
await new Promise((resolve) => setTimeout(resolve, 0));
}
但是,我不确定这是否是一个足够好的解决方案,因为它看起来很老套,而且乍一看并不明显发生了什么。我使用了一个自我解决的承诺,它在 0 毫秒超时后解决,这似乎没有做任何事情。然而,等待 Promise 的事实允许程序“放弃”异步处理,并同时处理正在刷新令牌的并行实例,从而允许
while
循环最终结束。
我还尝试删除 hackish 0 毫秒承诺,但随后该过程消耗了所有处理时间并且不允许并行实例刷新令牌。这导致网站挂起/冻结。
我希望该函数的两次调用并行运行,并且
while
循环会等待第二次调用完成,这将使对 isTokenPending()
的下一次调用返回 false
。
另外,我发现了这个帖子: 如何等待 JavaScript Promise 解析后再恢复功能?
但是,接受的答案表明没有办法实现这一点,而且它的日期是 9 年前,所以如果这甚至适用于我的问题,我想知道自那以后是否有任何变化。也许使用
yield
?
所以我真正要问的是,如何允许这部分代码(带有
while
循环)等待异步代码完成,然后在没有未解决的承诺时继续?
预先感谢您提出的解决方案。
我知道你想要什么,但我觉得它可能比需要的更复杂一点。
假设您的所有请求都使用相同的令牌,您可以在执行任何文档获取请求之前检查令牌的有效性。
这确保只完成一次刷新请求,并且所有文档获取请求都具有有效的令牌。我在想类似的事情
async validateToken() {
if(!isTokenValid) {
const refreshedToken = await refrehToken();
storeUpdatedToken(refreshedToken)
return refreshedToken;
}
return getStoredValidToken();
}
async function fetchDocuments(documentToFetch) {
const validToken = await validateToken()
for(let i = 0; i < documentToFetch; i ++) {
await fetchDocument(validToken);
}
}
现在,此代码将依次运行所有 fetchDocument 请求,这可能不是您想要的。这就是 的用武之地。该函数接受一组待处理的 Promise,只有当所有 Promise 都得到解决时才会得到解决。因此,您可以构建一个包含待处理承诺的数组,每个要获取的文档对应一个数组,然后
await
Promise.all
函数。
/* ... */
async function fetchDocuments(documentToFetch) {
const validToken = await validateToken()
const pendingPromises = []
for(let i = 0; i < documentToFetch; i ++) {
pendingPromises.push(fetchDocument(validToken));
}
await Promise.all(pendingPromises)
}
现在,所有文档获取请求都将并行运行,只有在获取所有文档后,fetchDocuments
函数才会解析。