实现JSONP方法
手写实现JSONP跨域请求方法,支持动态创建script标签、回调函数处理和超时控制
问题
JSONP(JSON with Padding)是一种解决跨域请求的方案。由于浏览器的同源策略限制,XMLHttpRequest无法直接进行跨域请求,但<script>标签不受此限制。JSONP利用这一特性,通过动态创建script标签来实现跨域数据获取。
需要实现一个JSONP方法,支持:
- 动态创建script标签发起请求
- 自动生成全局回调函数
- 请求成功/失败的处理
- 超时控制
- 资源清理
解答
function jsonp({ url, params = {}, callbackKey = 'callback', timeout = 10000 }) {
return new Promise((resolve, reject) => {
// 生成唯一的回调函数名
const callbackName = `jsonp_${Date.now()}_${Math.random().toString(36).substr(2)}`;
// 创建script标签
const script = document.createElement('script');
// 定义全局回调函数
window[callbackName] = (data) => {
// 请求成功,返回数据
resolve(data);
// 清理资源
cleanup();
};
// 构建请求参数
const queryParams = {
...params,
[callbackKey]: callbackName
};
// 将参数对象转换为查询字符串
const queryString = Object.keys(queryParams)
.map(key => `${encodeURIComponent(key)}=${encodeURIComponent(queryParams[key])}`)
.join('&');
// 拼接完整URL
const separator = url.includes('?') ? '&' : '?';
script.src = `${url}${separator}${queryString}`;
// 设置超时处理
let timer = null;
if (timeout) {
timer = setTimeout(() => {
reject(new Error('JSONP request timeout'));
cleanup();
}, timeout);
}
// 处理script加载错误
script.onerror = () => {
reject(new Error('JSONP request failed'));
cleanup();
};
// 清理函数
function cleanup() {
// 清除定时器
if (timer) {
clearTimeout(timer);
timer = null;
}
// 删除全局回调函数
if (window[callbackName]) {
delete window[callbackName];
}
// 移除script标签
if (script.parentNode) {
script.parentNode.removeChild(script);
}
}
// 将script标签添加到页面中,触发请求
document.body.appendChild(script);
});
}
使用示例
// 基本使用
jsonp({
url: 'https://api.example.com/data',
params: {
id: 123,
name: 'test'
}
})
.then(data => {
console.log('请求成功:', data);
})
.catch(error => {
console.error('请求失败:', error.message);
});
// 自定义回调参数名
jsonp({
url: 'https://api.example.com/user',
params: { userId: 456 },
callbackKey: 'cb', // 有些API使用cb而不是callback
timeout: 5000 // 5秒超时
})
.then(data => {
console.log('用户数据:', data);
})
.catch(error => {
console.error('获取用户数据失败:', error);
});
// 使用async/await
async function fetchData() {
try {
const result = await jsonp({
url: 'https://api.example.com/list',
params: { page: 1, size: 10 }
});
console.log('数据列表:', result);
} catch (error) {
console.error('请求出错:', error);
}
}
fetchData();
关键点
-
动态生成回调函数名:使用时间戳和随机数组合生成唯一的回调函数名,避免多个请求之间的冲突
-
Promise封装:将JSONP异步操作封装成Promise,使其支持现代的async/await语法,提升代码可读性
-
参数序列化:将参数对象转换为URL查询字符串,注意使用
encodeURIComponent进行编码,防止特殊字符导致的问题 -
超时控制:通过
setTimeout实现超时机制,避免请求长时间无响应导致的资源占用 -
资源清理:请求完成后(成功、失败或超时)必须清理全局回调函数和script标签,防止内存泄漏
-
错误处理:监听script的
onerror事件,捕获网络错误或404等情况 -
灵活配置:支持自定义回调参数名(callbackKey),因为不同API可能使用不同的参数名(如callback、cb、jsonp等)
目录