资讯专栏INFORMATION COLUMN

动手实现一个JSONP

Julylovin / 1439人阅读

摘要:但是当我们有多个请求,并且每个请求的处理都是不一样的时候,这个变量将会被覆盖。这是不行的,因此我们应该为每一次请求设置一个唯一且不会冲突的变量,因此增加一个生成名字的方法如下这时候我们会发现我们已经利用了一个类似于随机的形式完成了多次请求。

文章首发于我的github及个人博客,github请看https://github.com/huruji/blo...,转载请注明出处。

JSONP的原理就不细说了,就是利用script可以跨域的特点来实现跨域,首先我们考虑一个最简单的jsonp,就是简简单单创建script标签,
添加url的功能,如下:

function jsonp(url) {
  const script = document.createElement("script");
  script.src = url;
  script.type = "text/javascript";
  document.body.appendChild(script);
}

此时我们使用服务端的代码如下:

const http = require("http");
const data = {"data":"hello"};
const url = require("url");
const queryString = require("querystring");

http.createServer(function(req, res) {
  var params = url.parse(req.url);
  console.log(params);
  if(params.query && queryString.parse(params.query).callback) {
    console.log(1231232);
    const str = queryString.parse(params.query).callback + "(" + JSON.stringify(data) + ")";
    return res.end(str)
  }
  res.end(JSON.stringify(data));
}).listen(5000);

这是我们调用jsonp,假设我们只是想要alert出返回的数据,如下:

function msg(res) {
  alert(res.data);
}

jsonp("http://localhost:5000?callback=msg");

这时候我们运行代码可以发现已经正确弹出了相应的数据。
但是我们会发现这里的callback回调函数是一个全局的,这是不可取的,因此我们需要进行一些修改,将处理修改为一个局部的,我们可以将其作为一个回调函数来处理,如下:

function jsonp(url, callback) {
  window.jsonpCallback = callback;
  const script = document.createElement("script");
  script.src = url + "?callback=jsonpCallback";
  script.type = "text/javascript";
  document.body.appendChild(script);
}

jsonp("http://localhost:5000", function(res) {
  alert(res.data);
});

这时候我们会发现我们不再需要在url中声明相应的callback了,但是我们还是会发现一个问题,就是我们将所有的callback都设置成了一个全局变量,这样的原因是因为我们需要在数据请求完成之后调用这个方法,因此不得不设置为一个全局变量。但是当我们有多个请求,并且每个请求的处理都是不一样的时候,这个变量将会被覆盖。这是不行的,因此我们应该为每一次请求设置一个唯一且不会冲突的变量,因此增加一个生成callback名字的方法如下:

function generateJsonpCallback() {
  return `jsonpcallback_${Date.now()}_${Math.floor(Math.random() * 100000)}`;
}

function jsonp(url, callback) {
  const funcName = generateJsonpCallback();
  window[funcName] = callback;
  const script = document.createElement("script");
  script.src = `${url}?callback=${funcName}`;
  script.type = "text/javascript";
  document.body.appendChild(script);
}


jsonp("http://localhost:5000", function(res) {
  alert(res.data);
});

jsonp("http://localhost:5000", function(res) {
  const text = document.createTextNode(res.data);
  document.body.appendChild(text);
});

这时候我们会发现我们已经利用了一个类似于随机ID的形式完成了多次请求。
但是还是有一个问题大量的请求之后,window中会含有大量的全局变量,而且还有大量的script标签,这显然不是我们想要的,所以我们可以在请求完成之后删除变量和script标签。

function generateJsonpCallback() {
  return `jsonpcallback_${Date.now()}_${Math.floor(Math.random() * 100000)}`;
}

function removeScript(id) {
  document.body.removeChild(document.getElementById(id));
}

function removeFunc(name) {
  delete window[name];
}

function jsonp(url, timeout = 3000, callback) {
  const funcName = generateJsonpCallback();
  window[funcName] = callback;
  const script = document.createElement("script");
  script.src = `${url}?callback=${funcName}`;
  script.id = funcName;
  script.type = "text/javascript";
  document.body.appendChild(script);
  setTimeout(() => {
    removeScript(funcName);
    removeFunc(funcName);
  }, timeout)
}


jsonp("http://localhost:5000", 3000, function(res) {
  alert(res.data);
});

jsonp("http://localhost:5000", 3000, function(res) {
  const text = document.createTextNode(res.data);
  document.body.appendChild(text);
});

我们通过将利用一个timeout时间定时为我们清除相应的script标签和全局变量就可以了,这个定时时间的作用类似于ajax的timeout时间。
我们所有的内容都是使用es6的,那为什么不使用Promise来处理呢,还要使用烦人的回调,接下来那就来Promise化吧。

function jsonp(url, options = {timeout:3000}) {
  const timeout = options.timeout;
  return new Promise((resolve, reject) => {
    const funcName = generateJsonpCallback();
    window[funcName] = (res) => {
      resolve(res);
      setTimeout(() => {
        removeScript(funcName);
        removeFunc(funcName);
      }, timeout)
    };
    const script = document.createElement("script");
    script.src = `${url}?callback=${funcName}`;
    script.id = funcName;
    script.type = "text/javascript";
    document.body.appendChild(script);
  })
}

调用只需要如下就可以了

jsonp("http://localhost:5000").then((res) => alert(res.data));

jsonp("http://localhost:5000").then((res) => {
  const text = document.createTextNode(res.data);
  document.body.appendChild(text);
});

到目前为止,一个较为完整的jsonp就实现了,但是我们还是会觉得少了一些什么,相信你已经看出来了,那就是错误处理。
迄今为止,并没有测试过如果这个script标签加载不成功如何处理,判断资源加载失败,显然使用的是onerror事件,我们这就把他加上:

function jsonp(url, options = {timeout:3000}) {
  const timeout = options.timeout;
  let timeId;
  return new Promise((resolve, reject) => {
    const funcName = generateJsonpCallback();
    window[funcName] = (res) => {
      resolve(res);
      timeId = setTimeout(() => {
        removeScript(funcName);
        removeFunc(funcName);
      }, timeout)
    };
    const script = document.createElement("script");
    script.src = `${url}?callback=${funcName}`;
    script.id = funcName;
    script.type = "text/javascript";
    document.body.appendChild(script);
    script.onerror = () => {
      reject(new Error(`fetch ${url} failed`));
      removeScript(funcName);
      removeFunc(funcName);
      if(timeId) clearTimeout(timeId);
    }
  })
}

我们可以测试一下,输入一个不存在的url:

jsonp("http://localhost:7000").then((res) => alert(res.data));

可以发现这时正常处理错误了,可以在控制台看到相应的url获取失败,至此,完工;

至此所有的代码简单封装如下:

(function(global){
  function generateJsonpCallback() {
    return `jsonpcallback_${Date.now()}_${Math.floor(Math.random() * 100000)}`;
  }

  function removeScript(id) {
    document.body.removeChild(document.getElementById(id));
  }

  function removeFunc(name) {
    delete global[name];
  }

  function jsonp(url, options = {timeout:3000}) {
    const timeout = options.timeout;
    let timeId;
    return new Promise((resolve, reject) => {
      const funcName = generateJsonpCallback();
      global[funcName] = (res) => {
        resolve(res);
        timeId = setTimeout(() => {
          removeScript(funcName);
          removeFunc(funcName);
        }, timeout)
      };
      const script = document.createElement("script");
      script.src = `${url}?callback=${funcName}`;
      script.id = funcName;
      script.type = "text/javascript";
      document.body.appendChild(script);
      script.onerror = () => {
        reject(new Error(`fetch ${url} failed`));
        removeScript(funcName);
        removeFunc(funcName);
        if(timeId) clearTimeout(timeId);
      }
    })
  }
  window.jsonp = jsonp;
})(window);

测试代码如下:

jsonp("http://localhost:5000").then((res) => alert(res.data));

jsonp("http://localhost:5000").then((res) => {
  const text = document.createTextNode(res.data);
  document.body.appendChild(text);
});

jsonp("http://localhost:7000").then((res) => alert(res.data));

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/88769.html

相关文章

  • 自己动手,为vue写一个jsonp的方法

    摘要:它之后能够被使用到很多场景中其他处理请求和响应的方式,甚至任何需要生成自己的响应的方式。总结到这里都讲完了,其实没什么难度,主要是自己项目中遇到了,但是中没有这个方法啊。所以就想着实现了一个,因为其他的方法也都封装,不差这一个了。 Fetch 提供了对 Request 和 Response (以及其他与网络请求有关的)对象通用的定义。它之后能够被使用到很多场景中:service wor...

    dongxiawu 评论0 收藏0
  • jsonp的原理介绍及Promise封装

    摘要:什么叫是填充式或参数式的简写,是通过请求跨域接口,获取数据的新实现方式的实现原理动态创建标签,因为标签是没有同源策略限制,可以跨域的。具体看接下来的实现这个是库的具体实现,建议下载来研究一下,最好自己动手写一遍。 什么叫jsonp? jsonp是json with padding(填充式json或参数式json)的简写,是通过ajax请求跨域接口,获取数据的新实现方式 jsonp的实现...

    ninefive 评论0 收藏0
  • 想知道jsonp到底是怎么实现的?看我,包教会!

    摘要:不管你好不好,反正我是番茄酱。例如我们要请求的地址是获取用户信息后台应该返回给我们的数据是番茄酱也就是说我们最终需要的是服务器把格式的数据给我们。于是后台返回给我们番茄酱于是代码就相当于是番茄酱最终结果与相同。 不管你好不好,反正我是番茄酱。为啥要写这篇文章呢,因为我想写。 showImg(https://segmentfault.com/img/remote/146000001846...

    Edison 评论0 收藏0
  • 基于Vue.js的音乐播放器(Webapp)

    摘要:概述项目是基于,成品是一个移动端的音乐播放器,来源于的实战课程。播放器是全局组件,放在下面,通过传递数据,触发提交,从而使播放器开始工作。将请求的数据格式化后再通过传递,组件间共享,实现歌曲的播放切换等。 概述 项目是基于Vue.js,成品是一个移动端的音乐播放器,来源于imooc的实战课程。自己动手实践并加以修改拓展。项目的大致流程是Vue-cli构建开发环境,分析需求,设计构思,规...

    widuu 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<