Jsonp 跨域解决方案解析

由来背景
As we all know,浏览器同源策略会将非同源请求(跨域)抛弃,而许多时候我们可能并未将前端资源与服务端服务放在一个服务器,此时就需要一个跨域的手段了。
基于此,针对同源策略,衍生了一种跨域的方法Jsonp。
Jsonp简单讲,即利用了script标签不受浏览器同源策略影响的特性,从而利用script想服务端跨域请求的方式。
原理解析
图下图所示

封装一个Jsonp函数,Promise化
// jsonp.ts
type JsonpOptions<T extends Record<string, any>> = {
url: string,
timeout: number,
data: T,
cbKey?: string
};
interface JsonpRes {
code: 0 | -1,
data: any,
msg: string
}
export default function createJsonp<T extends Record<string, any>> (options: JsonpOptions<T>): Promise<JsonpRes> {
return new Promise((resolve, reject) => {
// 第一次请求,初始化随机数
if (!jsonpNumber) {
jsonpNumber = 1;
} else {
jsonpNumber++; // 拼接到callbackFn上,以防止多个jsonp请求时错误使用同一个callback
}
// callback接收函数名
const jsonpReceiverFnName = 'jsonpReceiver' + jsonpNumber;
const {
url,
timeout,
data,
cbKey = 'callback'
} = options;
let script: HTMLScriptElement; // 将被插入DOM的script标签
// 设置超时定时器
let timeoutTimer = setTimeout(() => {
reject({
code: -1,
data: null,
msg: '请求超时'
});
clear();
}, timeout);
// 清除上一次请求存的变量/定时器
function clear () {
window[jsonpReceiverFnName] = null; // 删除callbackFn
clearTimeout(timeoutTimer); // 清除定时器
script.parentNode?.removeChild(script); // 移除当前jsonp的script
}
window[jsonpReceiverFnName] = (res) => {
resolve({
code: 0,
data: res,
msg: '请求成功'
}); // 将数据返回到
clear(); // 请求成功后,清理本次jsonp带来的“副作用”
}
// 创建JSONP请求标签,发出请求
let params = ''; // 仅支持get请求,故只有query参数
// 遍历data,拼接参数
for (let key in data) {
if (data.hasOwnProperty(key)) {
params += `&${key}=${data[key]}`;
}
}
params = params.substring(1); // 移除第一个 &
script = document.createElement('script');
script.src = `${url}?${params}&${cbKey}=${jsonpReceiverFnName}`; // 拼接请求url
document.body.appendChild(script); // 插入body,正式发出jsonp请求
});
};
// 调用jsonp函数
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="./jsonp.js"></script>
</head>
<body>
<script>
async function testJsonp() {
try {
const res = await createJsonp({
url: 'https://x.x.x.x/testjsonp',
timeout: 15000,
data: {
id: '00145x',
level: 2
}
});
!res.code && console.log(res.data)
} catch (rejectRes) {
rejectRes.code && console.log(rejectRes?.msg)
}
}
testJsonp();
</script>
</body>
</html>

