JSONP 连续多次调用报错

先上代码,单次调用没有问题,连续两次调用就会出问题。

第二次调用 就会报如下错误:

Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.
const getJsonpData = (options) => {
  let script = document.createElement('script')
  
  return new Promise((resolve, reject) => {
    // option 结构示例
    // const options = {
    //   function: 'InitiateLock',
    //   sn
    // }
    const jsonParam = 'json=' + JSON.stringify(options)

    script.src = `http://127.0.0.1:17681/?callback=jsonpcallback&${jsonParam}`
    document.head.appendChild(script)
    window['jsonpcallback'] = (res) => {
      if (res[0].errcode === 0) {
        document.head.removeChild(script)
        resolve(res)
      } else {
        document.head.removeChild(script)
        reject(res)
      }
    }
    script.onerror = function (e) { // script链接不上时提示下载插件
      document.head.removeChild(script)
      reject(e)
    }
  })
}
阅读 5.5k
6 个回答
新手上路,请多包涵

上面两位的回答表面看没有问题,实际上你如果同时执行两次,写一个函数 在该函数体内调用该方法两次,两次传不同参数,因为接口固定回调函数函数名为 jsonpcallback ,这样就会导致先执行的在

window['jsonpcallback'] = (res) => {}

这里覆盖后面的。不知道大佬们有什么解决办法?

我试了一下 2 秒以后执行第二个函数就不会有问题,

那么可以在封装的时候使用节流的方法缓解该问题吗?

新手上路,请多包涵

removeChild 之前判断下这个 script 是否还存在,就可以了。

类似这样做一次判断,就不会出现这个报错了。
Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.

//   document.head.removeChild(script)
      script && document.head.removeChild(script)
const getJsonpData = (options) => {
  let script = document.createElement('script')
  
  return new Promise((resolve, reject) => {
    // option 结构示例
    // const options = {
    //   function: 'InitiateLock',
    //   sn
    // }
    const jsonParam = 'json=' + JSON.stringify(options)

    script.src = `http://127.0.0.1:17681/?callback=jsonpcallback&${jsonParam}`
    document.head.appendChild(script)
    window['jsonpcallback'] = (res) => {
      if (res[0].errcode === 0) {
        //   document.head.removeChild(script)
        script && document.head.removeChild(script)
        resolve(res)
      } else {
        //   document.head.removeChild(script)
        script && document.head.removeChild(script)
        reject(res)
      }
    }
    script.onerror = function (e) { // script链接不上时提示下载插件
    //   document.head.removeChild(script)
      script && document.head.removeChild(script)
      reject(e)
    }
  })
}

getJsonpData({name: '若川'});
新手上路,请多包涵

大佬。。我有点搞不懂,这个每次创建的回调函数有什么不一样吗,。为什么不提出来放在外面,在里面使用就好。。。emmm,可能是我还没怎么了解JsonP的用法。

原因在其他答案里分析得很到位了,就差解决方案了。
根据我对浏览器 DOM 解析的理解,如果给 scipt 元素添加 load 事件监听,script 元素引入的脚本执行完毕后,紧接着该 script 元素的 onload 回调被压入事件队列。
这样看来,JSONP 回调与 onload 回调二者的执行顺序是严格对应的,那么如果二者写/读同一个 FIFO 队列,应该可以做到内容的一一对应,就可以利用此特性实现 jsonp 的回调:

const getJsonpData = (()=>{
    // 占用全局命名空间,确保单例
    if(window.JSONP_GETTER){
        return window.JSONP_GETTER;
    }

    const jsonpFIFO = [], { head } = document;
    const clearScript = script => {
        head.removeChild(script);
    };
    
    // 结果写入队列
    window['jsonpcallback'] = jsonpFIFO.push.bind(jsonpFIFO);
    
    window.JSONP_GETTER =  options =>{
        let script = document.createElement('script');

        return new Promise((resolve, reject) => {
            const jsonParam = 'json=' + JSON.stringify(options);

            script.src = `http://127.0.0.1:17681/?callback=jsonpcallback&${jsonParam}`;

            script.onerror = e => {
                clearScript(script);
                reject(e);
            };

            script.onload = () => {
                // 从队列读出新鲜的结果
                const res = jsonpFIFO.shift();
                
                if (res && res[0] && res[0].errcode === 0) {
                    resolve(res);
                } else {
                    reject(res);
                }
                clearScript(script);
            };

            head.appendChild(script);
        });
    }
    return window.JSONP_GETTER
})();

当然,这是基于我对 DOM 解析的理解,如果理解与事实不符的话可能会出岔子。最好的可能性是后端支持 callback 变量,这样的话每次使用不同的函数名,用完清理掉就可以了。

我们先看这里。这里的 script 其实是个闭包jsonpcallback 是个字符常量。
问题来了,调用两次 script 的确不是同一个变量,但是 window 上的 jsonpcallback 方法呗覆盖了,也就导致只会调用最后一次的 removeChild

window['jsonpcallback'] = (res) => {
  if (res[0].errcode === 0) {
    document.head.removeChild(script)
    resolve(res)
  } else {
    document.head.removeChild(script)
    reject(res)
  }
}
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏