MST

星途 面试题库

面试题:JavaScript Fetch API与复杂场景应用

在一个Web应用中,需要使用Fetch API进行数据请求,但是服务器要求每次请求都携带特定的认证头信息,且这个认证信息会在一定时间后过期需要重新获取。请设计一个完整的机制,使用Fetch API实现自动认证头更新并处理请求,同时要考虑如何优雅地处理多个并发请求中认证头过期的情况,确保请求的稳定性和效率。
10.9万 热度难度
编程语言JavaScript

知识考点

AI 面试

面试题答案

一键面试
  1. 存储认证信息
    • 使用localStoragesessionStorage来存储认证头信息(如token)以及过期时间。例如:
    function saveAuthInfo(token, expiration) {
        localStorage.setItem('authToken', token);
        localStorage.setItem('tokenExpiration', expiration);
    }
    
  2. 获取认证信息
    • 编写函数从存储中获取认证信息,并检查是否过期。
    function getAuthInfo() {
        const token = localStorage.getItem('authToken');
        const expiration = localStorage.getItem('tokenExpiration');
        if (!token ||!expiration || Date.now() >= parseInt(expiration)) {
            return null;
        }
        return { token, expiration };
    }
    
  3. 更新认证信息
    • 编写函数向服务器请求新的认证信息,并更新存储。
    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;
        }
    }
    
  4. 创建自定义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;
        }
    }
    
  5. 处理并发请求
    • 使用一个标志变量(如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实现自动认证头更新并处理请求,同时优雅地处理多个并发请求中认证头过期的情况,确保请求的稳定性和效率。