JSONP前端流程

发布时间 2023-07-31 22:11:21作者: 方朝端

JSONP前端流程

JSONP总体思路

  1. 后端先给前端一个接口文档。
    • https://www.baidu.com/sugrec?prod=pc&wd=用户输入的文字&cb=后端要调用的前端定义的全局函数名
  2. 前端会封装一个jsonp函数:它的作用是传入特定参数,返回一个Promise实例。
    1. 会用jsonp的方式来请求后端数据,把后端数据通过Promise实例返回。

      • 此时这个Promise实例的状态为"pending"。
    2. 它内部会创建一个临时的唯一方法名jQuery_202301010101。并自动生成一个全局函数,该函数的功能就是把传入的入参resolve()出去。

      window[`jQuery_202301010101`] = function (data) {
        resolve(data); //把data传递给了resolve函数,也就是传递给了成功的回调函数,所以jsonp后面的then就会执行了。
      }
      
    3. 并把这个临时的唯一方法名通过构建一个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'}]}

  3. 用户触发事件,会调用jsonp函数,得到一个Promise实例。此时代码就执行到了jsonp函数中。
    1. 一开始这个Promise实例的状态为"pending",在后端返回一段动态的js代码并在浏览器中执行后,该Promise实例的状态就变成"fulfilled",同时then()中得到的结果值就是后端返回的json数据。
    2. 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`);
})

进阶参考