JSONP前端流程
JSONP总体思路
- 后端先给前端一个接口文档。
- 如
https://www.baidu.com/sugrec?prod=pc&wd=用户输入的文字&cb=后端要调用的前端定义的全局函数名
。
- 如
- 前端会封装一个jsonp函数:它的作用是传入特定参数,返回一个Promise实例。
-
会用jsonp的方式来请求后端数据,把后端数据通过Promise实例返回。
- 此时这个Promise实例的状态为"pending"。
-
它内部会创建一个
临时的唯一方法名
如jQuery_202301010101
。并自动生成一个全局函数,该函数的功能就是把传入的入参resolve()出去。window[`jQuery_202301010101`] = function (data) { resolve(data); //把data传递给了resolve函数,也就是传递给了成功的回调函数,所以jsonp后面的then就会执行了。 }
-
并把这个
临时的唯一方法名
通过构建一个script标签通过get发送请求传递给后端。-
之后后端会返回一段动态的js代码,如
临时的唯一方法名(前端想要的数据)
如jQuery_202301010101({name:'6666',list:[{id:1,text:'文字1'},{id:2,text:'文字2'}]})
。 -
浏览器会把后端返回的
jQuery_202301010101({name:'6666',list:[{id:1,text:'文字1'},{id:2,text:'文字2'}]})
当成js代码在全局作用域中执行。jQuery_202301010101({name:'6666',list:[{id:1,text:'文字1'},{id:2,text:'文字2'}]}) //相当于:window[`jQuery_202301010101`]({name:'6666',list:[{id:1,text:'文字1'},{id:2,text:'文字2'}]}) window[`jQuery_202301010101`] = function (data) { resolve(data); //把data传递给了resolve函数,也就是传递给了成功的回调函数,所以jsonp后面的then就会执行了。 }
-
此时这个Promise实例的状态为"fulfilled",值就为后端要返回的json数据,如
{name:'6666',list:[{id:1,text:'文字1'},{id:2,text:'文字2'}]}
。
-
-
- 用户触发事件,会调用jsonp函数,得到一个Promise实例。此时代码就执行到了jsonp函数中。
- 一开始这个Promise实例的状态为"pending",在后端返回一段动态的js代码并在浏览器中执行后,该Promise实例的状态就变成"fulfilled",同时then()中得到的结果值就是后端返回的json数据。
- Promise实例的状态变成"fulfilled"后,通过then()拿到后端返回的json数据,如
{name:'6666',list:[{id:1,text:'文字1'},{id:2,text:'文字2'}]}
,并重新渲染页面。
代码说明
<!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>
</head>
<body>
<input id="search-word" />
<ul id="suggestions"></ul>
<script>
function jsonp(options) {
return new Promise((resolve, reject) => {
const { url, jsonp, data } = options;
let callbackName = `jQuery_${Date.now()}`; //1.或者创建一个临时的、唯一的方法名。
//给window添加一个全局变量,变量名为方法名,值是一个只会给后端调用一次的函数。
window[callbackName] = function (data) {
delete window[callbackName];
document.body.removeChild(script);
resolve(data); //把data传递给了resolve函数,也就是传递给了成功的回调函数,所以jsonp后面的then就会执行了。
};
//动态创建一个类型为script的对象或者说元素
let script = document.createElement("script");
let queryStr = url.indexOf("?") === -1 ? "?" : "&";
for (let key in data) {
queryStr += `${key}=${data[key]}&`;
}
script.src = `${url}${queryStr}${jsonp}=${callbackName}`; //得到一个url地址。
script.onerror = (error) => reject(error);
document.body.appendChild(script); //向body的尾部添加一个script对象,之后浏览器就会向服务器发起一个get请求。请求回来的其实是字符串,但浏览器会把那些字符串当成js代码来执行。//后端返回的数据格式为:前端传递给的函数名(前端想要的数据对象) 如:jQuery_998888({name:'前端想要的数据',...}) //浏览器在拿到这个后端返回的数据后,就相当于在全局作用域下运行了一遍后端的代码。
});
}
let searchWord = document.getElementById("search-word");
let suggestions = document.getElementById("suggestions");
const handleInput = (event) => {
let wd = event.target.value;
const thePromise = jsonp({
url: "https://www.baidu.com/sugrec", //也可能为https://www.baidu.com/sugrec?prod=pc
jsonp: "cb", //百度的后端规定了这个字段就叫cb。但网易的可能的约定为callback或者cbf之类的,反正是后端规定的。
data: { prod: "pc", wd }, //其它要传递给服务器的数据,它们都会拼接到查询字符串中。这个有些字段是前端定的,有些字段是后端定的。
});
thePromise
.then((result) => {
//这个就是后端返回的 jQuery_998888({name:'前端想要的数据',...}) 中的 {name:'前端想要的数据',...}。
let { g } = result;
if (g) {
0;
let html = "";
for (let i = 0; i < g.length; i++) {
html += `<li>${g[i].q}</li>`;
}
suggestions.innerHTML = html;
}
})
.catch((error) => {
console.log(error);
});
};
searchWord.addEventListener("input", handleInput);
</script>
</body>
</html>
前后端交互模板
- fang/f20230731/1.basic/doc/jsonp.html 这个就是前端写的代码。
<!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>jsonp</title>
</head>
<body>
动态jsonp。这里是自动生成的`Live Server`起起来的;
<script>
let callbackName = `JQuery_${Date.now()}`;
console.log(`动态函数变量名:callbackName-->`, callbackName);
window[callbackName] = async (data) => {
console.log(`后端返回的json数据:data-->`, data);
await new Promise((resolve, reject) => {
setTimeout(() => {
resolve(data666);
}, 300000);
});
// 执行结束后,移除多余代码。
console.log(`执行结束后,移除多余代码。`);
window[callbackName] = null;
document.body.removeChild(script);
};
let script = document.createElement("script");
script.src = `http://localhost:8111/sugrec.js?cb=${callbackName}`;
console.log(`动态生成的脚本标签:script-->`, script);
document.body.appendChild(script);
</script>
</body>
</html>
- fang/f20230731/1.basic/doc/jsonp.js 这个是后端服务器的代码。
// 这里是用node起起来的,服务器端口为8111;
const http = require('http')
const url = require('url')
http.createServer((req, res) => {
res.setHeader('Content-Type', 'application/javascript')
const { query: { cb } } = url.parse(req.url || '', true)//这里是为了让后端拿到前端定义的那个函数的函数名。
console.log(`cb-->`, cb);//这里是在命令行中打印的。
const jsonObj = { 'id': 100, name: '这个是后端构建出来的json数据' }//这个就是后端要返回的json数据。
//let script = `${cb}(${jsonStr})`//实际上大多数的jsonp交互中,后端返回的代码就是这个格式。
const jsonStr = JSON.stringify(jsonObj)
let script = `
// 这里是服务器那边动态生成的js代码。
console.log('开始执行服务器返回的动态js代码。')
${cb}(${jsonStr});
console.log('结束执行服务器返回的动态js代码。')
`
res.end(script);
}).listen(8111, () => {
console.log(`服务器地址为: http://localhost:8111`);
})