As we all know,瀏覽器同源策略會將非同源請求(跨域)拋棄,而許多時候我們可能併未將前端資源與服務端服務放在一個服務器,此時就需要一個跨域的手段了。
基於此,針對同源策略,衍生了一種跨域的方法Jsonp。
Jsonp簡單講,即利用了script標簽不受瀏覽器同源策略影響的特性,從而利用script想服務端跨域請求的方式。
// 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>