带你用 Node 了解 JSONP 实现原理

1、JSONP 是什么,解决了什么问题

JSONP 指的是 JSON with Padding。

由于跨域政策,从另一个域请求文件会引起问题。

从另一个域请求外部脚本没有这个问题。

JSONP 利用了这个优势,并使用 script 标签替代 XMLHttpRequest 对象。——w3c

Web 页面上调用 js 文件时不受是否跨域的影响(不仅如此,我们还发现凡是拥有src这个属性的标签都拥有跨域的能力,比如 )所以 JSONP 解决的问题是请求跨域的问题。如果还是不理解可以自行百度相关解释,这里不做重点解释,主要是看一下实现。

2、如何实现 JSONP

了解了 JSONP 是什么,我们就来模拟一下一个 JSONP 请求。JSONP 的实现方式一般是在请求参数里添加"callback=函数名"的方式,这里我们使用 nodejs 作为服务。

首先搭建一个本地服务,这里使用 express,先全局安装一下 express

npm install express -g

然后创建一个文件夹,进入文件夹目录下执行下面命令

npm init

这里一路回车就可以

安装 express

npm install express -s

这个时候我们的目录是这样的

├── node_modules
├── package-lock.json
└── package.json

在根目录下新建 index.js 文件

├── index.js
├── node_modules
├── package-lock.json
└── package.json

文件内容如下

var express = require('express')

//2. 创建express服务器
var server = express()

//3. 访问服务器(get或者post)
//参数一: 请求根路径
//3.1 get请求
server.get('/', function(request, response) {
  // console.log(request)
  response.send({ name: 'starfishing' })
})
server.get('/json/getUser', function(request, response) {
  console.log(request.query)
  let callback = request.query.callback
  // console.log(request)
  let str = callback + "({'name':'starfishing','age':22})"
  response.send(str)
})
//4. 绑定端口
server.listen(4040)
console.log('启动4040')

到这里我们的服务就搭好了,执行 node index.js 启动服务

然后新建一个 html 页面,内容如下

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>JSONP测试</title>
  </head>
  <body>
    <script>
      function jsonp(data) {
        // 这是我们的回调函数
        console.log(data.name, data.age)
      }
    </script>
    <script src="http://localhost:4040/json/getUser/?callback=jsonp"></script>
  </body>
</html>

在浏览器打开页面,调出控制台,可以看到我们回调函数打印出的内容

3、jsonp 进阶,使用 promise 实现 jsonp

我们会基于 promise 封装一个 JSONP 函数,包括请求超时逻辑,下面贴一下代码

function JSONP(url, params, overtimes) {
  return new Promise(function(resolve, reject) {
    let query = []
    //注册回调函数
    let callbackName = 'jsonp' + Math.ceil(Math.random() * 1000000)
    window[callbackName] = function(json) {
      head.removeChild(script) //移除scipt标签
      script.timer ? clearTimeout(script.timer) : false //清除超时计时器
      window[callbackName] = null
      resolve(json) //成功处理
    }

    for (let key in params) {
      query.push(key + '=' + params[key])
    }
    query.push('callback=' + callbackName)
    let remote = url + '/?' + query.join('&')
    let script = document.createElement('script')
    script.src = remote
    let head = document.getElementsByTagName('head')[0]
    head.appendChild(script)

    script.onerror = function(err) {
      if (window && window[callbackName]) {
        delete window[callbackName]
      }
      try {
        head.removeChild(script)
      } catch {}

      reject(err)
    }

    if (overtimes) {
      script.timer = setTimeout(function() {
        if (window && window[callbackName]) {
          delete window[callbackName]
        }
        try {
          head.removeChild(script)
        } catch {}

        reject({ timer: 'overtime' })
      }, overtimes)
    }
  })
}

然后对我们的 node 服务做一下改造来验证我们的函数

var express = require('express')

//2. 创建express服务器
var server = express()

//3. 访问服务器(get或者post)
//参数一: 请求根路径
//3.1 get请求
server.get('/', function(request, response) {
  // console.log(request)
  response.send({ name: 'xiaoxin' })
})
server.get('/json/getUser', function(request, response) {
  console.log(request.query)
  let callback = request.query.callback
  let str = callback + "({'name':'starfishing','age':22})"
  // 下面是验证正常返回的情况
  response.send(str)

  // 下面是验证服务器错误的情况
  // response.status(500)

  // 下面验证超时
  // setTimeout(function() {
  //   response.send(str)
  // }, 6000)
})
//4. 绑定端口
server.listen(4040)
console.log('启动4040')

最后把新的函数放到 HTML 文件中进行验证

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>JSONP测试</title>
  </head>
  <body>
    <script>
      function JSONP(url, params, overtimes) {
        // 这里就是我们上面封装的函数,直接拷贝到这里,避免篇幅太长,这里就不拷进来了
      }
    </script>
    <script>
      JSONP('http://localhost:4040/json/getUser', { name: 'haha' }, 5000)
        .then(data => {
          console.log(data)
        })
        .catch(err => {
          console.log(err)
        })
    </script>
  </body>
</html>
上次更新: 3/22/2020, 1:35:55 PM