Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

路过微博,盲写了一个JSONP的,未实测 #1

Open
woota opened this issue Mar 10, 2016 · 15 comments
Open

路过微博,盲写了一个JSONP的,未实测 #1

woota opened this issue Mar 10, 2016 · 15 comments

Comments

@woota
Copy link

woota commented Mar 10, 2016

var JSONP = (url, descriptor) => {
  var script = document.createElement('script')
  var body = document.getElementsByTagName('body')[0]

  var parseParam = (paramObj) => {
    var paramStr = '?',
        prop = ''
    for (prop in paramObj) {
      paramStr += (`${prop}=${paramObj[prop]}&`)
    }

    return paramStr
  }

  var funcName = 'jsonpCb'
  var params = parseParam(descriptor.data)
  window[funcName] = descriptor.callback

  script.src = url + params + 'callback=' + funcName
  body.appendChild(script)
}
@island205
Copy link
Owner

  1. for (prop in paramObj) { 要做 hasOwnProperty 检查;
  2. window[funcName] = descriptor.callback 一是暴露全局变量,二是会产生严重的 bug,同时发出多个 JSONP 请求的时候,请求还没回来,callback 就已经被修改了。

@zswang
Copy link

zswang commented Mar 10, 2016

  1. ES6 用得不纯,let 跑哪去了? 🐹
  2. 参数没有 uri 编码 👯
JSONP(url, {
  a: '&b=2&c=3',
  b: 3
});

混眼熟。

@island205
Copy link
Owner

@zswang 这段代码问题太多了……

@woota
Copy link
Author

woota commented Mar 10, 2016

贴自己的代码有一个好,就是优秀的工程师会指出不足,然后自己能够有所精进。才吃饭的功夫就有两位老师提出了问题,我一个一个来看。

@island205

1.这种调用的传参,多数情况下都是传入纯js对象,所以偷了懒。不过总归有隐患,该是检查的。
2.我倒没有考虑多次调用的情况,来改一下:

var JSONP = (url, descriptor) => {
  var script = document.createElement('script')
  var body = document.getElementsByTagName('body')[0]

  var parseParam = (paramObj) => {
    var paramStr = '?',
        prop = ''
    for (prop in paramObj) {
      if(paramObj.hasOwnProperty(prop)) {
        paramStr += (`${prop}=${paramObj[prop]}&`)
      }
    }

    return paramStr
  }

  var params = parseParam(descriptor.data)
  var index = JSONP.cbs.push(descriptor.callback) - 1

  script.src = url + params + `callback=JSONP.cbs[${index}]`
  body.appendChild(script)
}

JSONP.cbs = []

志寸老师看看代码哪里还有改进的空间

@lijsh
Copy link

lijsh commented Mar 10, 2016

虽然楼上都写得差不多了,还是贴一下我的吧:

const JSONP = (url, jsonpObj) => {
  let cbName = "cb" + JSONP.count++
  let cbQuery = "JSONP." + cbName
  let paramsToQuery = obj => {
    let query = '?'
    for (let k in obj) {
      if (obj.hasOwnProperty(k)) {
        query += `${k}=${obj[k]}&`
      }      
    }
    return query
  }
  JSONP[cbName] = data => {
    try {
      jsonpObj.callback(data)
    } finally {
      delete JSONP[cbName]
      document.body.removeChild(script)
    }   
  }
  let queryStr = paramsToQuery(jsonpObj.data) + 'callback=' + cbQuery
  let script = document.createElement('script')
  script.src = url + encodeURIComponent(queryStr)
  document.body.appendChild(script)
}
JSONP.count = 0

应该还要考虑url追加查询参数的情况,懒得写了。

@woota
Copy link
Author

woota commented Mar 10, 2016

@zswang

这位老师提的两个问题:

1.这段代码是在控制台下写的,对constlet关键字支持不太好,所以一律用了var
2.这是我的问题,也来改正一下:

var JSONP = (url, descriptor) => {
  var script = document.createElement('script')
  var body = document.getElementsByTagName('body')[0]

  var parseParam = (paramObj) => {
    var paramStr = '?',
        prop = ''
    for (prop in paramObj) {
      if(paramObj.hasOwnProperty(prop)) {
        paramStr += (`${prop}=${encodeURIComponent(paramObj[prop])}&`)
      }
    }

    return paramStr
  }

  var params = parseParam(descriptor.data)
  var index = JSONP.cbs.push(descriptor.callback) - 1

  script.src = url + params + 'callback=' + encodeURIComponent(`JSONP.cbs[${index}]`)
  body.appendChild(script)
}

JSONP.cbs = []

@woota
Copy link
Author

woota commented Mar 10, 2016

@lijsh

受这位同学的启发,利用闭包移除回调和script签:

var JSONP = (url, descriptor) => {
  var script = document.createElement('script')
  var body = document.getElementsByTagName('body')[0]

  var parseParam = (paramObj) => {
    var paramStr = '?',
        prop = ''
    for (prop in paramObj) {
      if(paramObj.hasOwnProperty(prop)) {
        paramStr += (`${prop}=${encodeURIComponent(paramObj[prop])}&`)
      }
    }

    return paramStr
  }

  var params = parseParam(descriptor.data)
  var callback = (data) => {
    descriptor.callback(data)
    body.removeChild(script)
    JSONP.cbs = [...JSONP.cbs.slice(0, index), ...JSONP.cbs.slice(index+1)]
  }
  var index = JSONP.cbs.push(callback) - 1

  script.src = url + params + 'callback=' + encodeURIComponent(`JSONP.cbs[${index}]`)
  body.appendChild(script)
}

JSONP.cbs = []

@island205
Copy link
Owner

@woota @lijsh 看两位的代码我也学到了很多。但 @woota 还有一个和刚才一样严重的 bug,虽然用 index 记录了 callback 在 JSONP.cbs 的位置,但是如果有多个 JSONP 请求发出,JSONP.cbs 中有多个 callback,如果有请求回调回来,indexJSONP.cbs 中的 callback 有可能就对应不上了。刻舟求剑就是这个道理。JSONP.cbs 像水流一样变化了。采用 @lijsh 用对象来索引 callback 是比较好处理的。

@woota
Copy link
Author

woota commented Mar 10, 2016

@island205

对哦,不过也可以delete掉JSONP.cbs[index],这样索引的位置不会改变。

var JSONP = (url, descriptor) => {
  var script = document.createElement('script')
  var body = document.getElementsByTagName('body')[0]

  var parseParam = (paramObj) => {
    var paramStr = '?',
        prop = ''
    for (prop in paramObj) {
      if(paramObj.hasOwnProperty(prop)) {
        paramStr += (`${prop}=${encodeURIComponent(paramObj[prop])}&`)
      }
    }

    return paramStr
  }

  var params = parseParam(descriptor.data)
  var callback = (data) => {
    descriptor.callback(data)
    body.removeChild(script)
    delete JSONP.cbs[index]
  }
  var index = JSONP.cbs.push(callback) - 1

  script.src = url + params + 'callback=' + encodeURIComponent(`JSONP.cbs[${index}]`)
  body.appendChild(script)
}

JSONP.cbs = []

@xlitter
Copy link

xlitter commented Mar 10, 2016

写了一个 @island205

function jsonP(url, settings) {
  'use strict';

  return new Promise((resolve, reject) => {
    let callbackId = -1;
    const jsonpCallback = settings.callback;
    if (!jsonP.seed) {
      callbackId = jsonP.seed = 1;
    } else {
      callbackId = ++jsonP.seed;
    }

    if (!jsonP.callbacks) {
      jsonP.callbacks = {};
    }
    const callbackName = `callback_${callbackId}`
    jsonP.callbacks[callbackName] = function (data) {
      jsonpCallback && jsonpCallback(data);
      jsonP.callbacks[callbackName].data = data;
      jsonP.callbacks[callbackName].isCalled = true;
    }

    const data = Object.assign({}, { jsoncallback: `jsonP.callbacks.${callbackName}` }, settings.data || {});

    const params = Object.keys(data).map((k) => {
      const value = data[k];
      if (value || (typeof value === 'number' && value === value) || value === '') {
        return `${encodeURIComponent(k)}=${encodeURIComponent(value)}`
      } else {
        return `${encodeURIComponent(k)}`;
      }
    }).join('&');

    url = url + (url.indexOf('?') !== -1 ? '&' : '?') + params;

    function jsonpReq(url) {
      const script = document.createElement('script');
      script.src = url;
      script.async = true;

      function cb(e) {

        const handle = jsonP.callbacks[callbackName]
        if (e.type === 'load' && handle.isCalled) {
          resolve(handle.data);
        } else {
          reject('error');
        }
        delete jsonP.callbacks[callbackName];
        document.body.removeChild(script);
        console.log(jsonP.callbacks);
      }

      script.addEventListener('load', cb, false);
      script.addEventListener('error', cb, false);

      document.body.appendChild(script);

    }

    jsonpReq(url);
  });

}

@island205
Copy link
Owner

@xlitter 我还看不出有什么问题,👍 看来要写测试用例了。

@GoBrianGo
Copy link

@island205 用timestamp这种方式来标记回调函数如何?

function jsonP(url, params) {
    var count = +new Date(),
        funcName = 'jsonp.cb' + count,
        data = params.data, script;

    url = url + '?jsonp=' + funcName;

    for (var item in data) {
        if (data.hasOwnProperty(item)) {
            url = url + '&' + encodeURIComponent(item) + '=' + encodeURIComponent(data[item]);
        }
    }

    script = document.createElement('script');
    script.src = url;

    jsonp[funcName] = function(res) {
        params.success(res);
        jsonp[funcName] = null;
    }

    document.body.appendChild(script);

    script.addEventListener('load', removeScript, false);
    script.addEventListener('error', removeScript, false);

    function removeScript() {
        document.body.removeChild(script);
    }
}

@island205
Copy link
Owner

@GoBrianGo 不确定,从原理上看无法保证两个 new Date() 一定是不一样的时间。

@GoBrianGo
Copy link

@island205 也是,还是用对象来索引比较靠谱。

@Leotw
Copy link

Leotw commented Mar 14, 2017

看到各位的代码,让我受教了很多。一个小小的jsonp能涉及很多基础细节。平时只忙着赶项目,研究各种新框架,忽略了基础的重要,是得花时间巩固一下了。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants