面试题答案
一键面试- 存储认证信息:
- 使用
localStorage
或sessionStorage
来存储认证头信息(如token
)以及过期时间。例如:
function saveAuthInfo(token, expiration) { localStorage.setItem('authToken', token); localStorage.setItem('tokenExpiration', expiration); }
- 使用
- 获取认证信息:
- 编写函数从存储中获取认证信息,并检查是否过期。
function getAuthInfo() { const token = localStorage.getItem('authToken'); const expiration = localStorage.getItem('tokenExpiration'); if (!token ||!expiration || Date.now() >= parseInt(expiration)) { return null; } return { token, expiration }; }
- 更新认证信息:
- 编写函数向服务器请求新的认证信息,并更新存储。
async function updateAuthInfo() { try { const response = await fetch('/api/refresh - token', { method: 'POST', headers: { 'Content - Type': 'application/json' } }); if (response.ok) { const data = await response.json(); const newToken = data.token; const newExpiration = Date.now() + data.expiresIn * 1000; saveAuthInfo(newToken, newExpiration); return { token: newToken, expiration: newExpiration }; } else { throw new Error('Failed to refresh token'); } } catch (error) { console.error('Error refreshing token:', error); return null; } }
- 创建自定义Fetch函数:
- 编写一个包装
fetch
的函数,自动处理认证头并在过期时更新。
async function customFetch(url, options = {}) { let authInfo = getAuthInfo(); if (!authInfo) { authInfo = await updateAuthInfo(); if (!authInfo) { throw new Error('Could not obtain valid authentication token'); } } const headers = { ...options.headers, 'Authorization': `Bearer ${authInfo.token}` }; const fetchOptions = { ...options, headers }; let response; try { response = await fetch(url, fetchOptions); if (response.status === 401) { // 认证失败,重新获取token并再次请求 authInfo = await updateAuthInfo(); if (!authInfo) { throw new Error('Could not obtain valid authentication token after 401'); } const newHeaders = { ...options.headers, 'Authorization': `Bearer ${authInfo.token}` }; const newFetchOptions = { ...options, headers: newHeaders }; response = await fetch(url, newFetchOptions); } return response; } catch (error) { console.error('Fetch error:', error); throw error; } }
- 编写一个包装
- 处理并发请求:
- 使用一个标志变量(如
isRefreshing
)来防止多个并发请求同时触发认证头更新。 - 同时,使用一个队列来存储在认证头更新期间发起的请求,等更新完成后依次处理。
let isRefreshing = false; const requestQueue = []; async function customFetchConcurrent(url, options = {}) { let authInfo = getAuthInfo(); if (!authInfo) { if (!isRefreshing) { isRefreshing = true; authInfo = await updateAuthInfo(); if (!authInfo) { throw new Error('Could not obtain valid authentication token'); } isRefreshing = false; while (requestQueue.length > 0) { const { queuedUrl, queuedOptions } = requestQueue.shift(); customFetchConcurrent(queuedUrl, queuedOptions); } } else { return new Promise((resolve, reject) => { requestQueue.push({ url, options, resolve, reject }); }); } } const headers = { ...options.headers, 'Authorization': `Bearer ${authInfo.token}` }; const fetchOptions = { ...options, headers }; let response; try { response = await fetch(url, fetchOptions); if (response.status === 401) { if (!isRefreshing) { isRefreshing = true; authInfo = await updateAuthInfo(); if (!authInfo) { throw new Error('Could not obtain valid authentication token after 401'); } isRefreshing = false; while (requestQueue.length > 0) { const { queuedUrl, queuedOptions } = requestQueue.shift(); customFetchConcurrent(queuedUrl, queuedOptions); } const newHeaders = { ...options.headers, 'Authorization': `Bearer ${authInfo.token}` }; const newFetchOptions = { ...options, headers: newHeaders }; response = await fetch(url, newFetchOptions); } else { return new Promise((resolve, reject) => { requestQueue.push({ url, options, resolve, reject }); }); } } return response; } catch (error) { console.error('Fetch error:', error); throw error; } }
- 使用一个标志变量(如
这样,通过上述机制,我们可以使用Fetch API实现自动认证头更新并处理请求,同时优雅地处理多个并发请求中认证头过期的情况,确保请求的稳定性和效率。