向JSONP回调函数传递额外参数

11
对于我的一个项目,我需要使用JSONP对一个(远程)API进行多次调用以处理API响应。所有调用都使用相同的回调函数。所有调用都是在客户端使用JavaScript动态生成的。
问题如下:我该如何传递附加参数给回调函数,以便告知函数我使用了哪些请求参数。例如,在以下示例中,我需要 myCallback 函数了解 id=123 的情况。
<script src="http://remote.host.com/api?id=123&jsonp=myCallback"></script>
有没有办法在不为每个调用创建一个单独的回调函数的情况下实现这一点?最好使用原生JavaScript解决方案。
编辑:
根据第一个评论和答案,出现了以下几点:
- 我对远程服务器没有任何控制。因此,将参数添加到响应中不是选项。 - 我同时发起多个请求,因此存储我的参数的任何变量都无法解决问题。 - 我知道我可以即时创建多个回调并进行分配。但问题是,是否可以以某种方式避免这种情况。如果没有其他解决方案出现,这将是我的后备计划。

你能让服务器在响应中返回id吗?服务器知道这个id,所以它可以直接将其放入响应代码中给你。 - jfriend00
1
你正在做必须完成的事情,其余的就取决于远程主机是否能够识别你的参数。 - Diodeus - James MacFarlane
@jfriend00 我没有对远程服务器的控制权,所以那里不会有任何更改。 - Sirko
@FelixKling 我更喜欢一个纯JavaScript的解决方案。 - Sirko
1
JSONP调用是由代码动态生成的吗?还是静态地包含在您的页面中?如果是由代码动态生成的,那么就有更多的选项。 - jfriend00
显示剩余3条评论
4个回答

10

以下是您的选项:

  1. 让服务器将ID放入响应中。这是最干净的方法,但通常您无法更改服务器代码。
  2. 如果您能保证永远不会有多个JSONP调用同时涉及到该ID,则可以将ID值填入全局变量中,当回调被调用时,从全局变量中获取ID值。这很简单,但是很脆弱,因为如果同时存在多个涉及到相同的ID的 JSONP 调用,它们将互相影响并且某些内容将无法正常工作。
  3. 为每个 JSONP 调用生成唯一的函数名称,并使用与该函数名称相关联的函数闭包来连接 ID 和回调。

这是第三种选择的示例:

您可以使用闭包来为您跟踪变量,但由于您可能同时有多个 JSON 调用正在进行中,因此必须使用动态生成的全局可访问函数名称,该名称对于每个连续的 JSONP 调用都是唯一的。 它可以像这样工作:

假设生成用于 JSONP 的 标签的函数类似于下面这样(您可以替换为当前使用的任何内容):

function doJSONP(url, callbackFuncName) {
   var fullURL = url + "&" + callbackFuncName;
   // generate the script tag here
}

然后,你可以在外面再定义一个函数来实现这个功能:

// global var
var jsonpCallbacks = {cntr: 0};


function getDataForId(url, id, fn) {
    // create a globally unique function name
    var name = "fn" + jsonpCallbacks.cntr++;

    // put that function in a globally accessible place for JSONP to call
    jsonpCallbacks[name] = function() {
        // upon success, remove the name
        delete jsonpCallbacks[name];
        // now call the desired callback internally and pass it the id
        var args = Array.prototype.slice.call(arguments);
        args.unshift(id);
        fn.apply(this, args);
    }

    doJSONP(url, "jsonpCallbacks." + name);
}

您的主要代码将调用 getDataForId(),并将传递给它的回调函数传递id值,如下所示,然后是JSONP在该函数上具有的任何其他参数:

getDataForId(123, "http://remote.host.com/api?id=123", function(id, /* other args here*/) {
    // you can process the returned data here with id available as the argument
});

这就是我心中的备选计划。然而,这意味着创建(虽然是动态的)多个回调函数,而我本来想避免这种情况。 - Sirko
@Sirko - 除非您能保证一次只有一个此类JSONP请求在进行中,否则您必须执行此类操作以将请求的状态与返回匹配。从定义上来说,JSONP非常愚蠢。它所做的就是调用全局函数。而且,您已经取消了让服务器为您解决此问题的可能性。所以,您必须将ID存储在可访问的位置。如果您想要唯一地将正确的ID与正确的函数调用相匹配,并且同时可能存在多个函数调用,则每个函数调用必须是全局唯一的。 - jfriend00
@Sirko - 如果您一次只有一个JSONP调用在进行中(并且您可以保证这种情况),那么只需将ID值存储在全局变量中,并从回调函数中检索它即可。这是一种脆弱的方法,因为道路上其他人的微小代码更改可能会轻易地破坏它,当他们意外地同时发起两个JSONP调用时,解决问题将会很麻烦。 - jfriend00
FYI,我知道YUI在其JSONP处理中做了类似的事情,以便将状态附加到给定的JSONP调用。这有点丑陋(JSONP本身就是一个巨大的hack),但丑陋只需要实现一次,然后就可以干净地使用它。 - jfriend00
@Sirko - 我已在答案开头概括了你的选项。 - jfriend00

1
有一种更简单的方法。在 URL 后面添加参数 '?', 然后在回调函数中按照以下方式访问它。
var url = "yourURL";
    url += "?"+"yourparameter";
    $.jsonp({
        url: url,
        cache: true,
        callbackParameter: "callback",
        callback: "cb",
        success: onreceive,
        error: function () {
            console.log("data error");
        }
    });

以下是回调函数的代码:
function onreceive(response,temp,k){
  var data = k.url.split("?");
  alert(data[1]);   //gives out your parameter
 }

注意:如果您已经在URL中有其他参数,可以更好地在URL中附加该参数。我这里只是展示了一种快速而粗糙的解决方案。

0

由于似乎我无法评论,所以我必须写一个答案。我按照jfriend00的说明进行了操作,但在我的回调中没有收到来自服务器的实际响应。最终我做的是:

var callbacks = {};

function doJsonCallWithExtraParams(url, id, renderCallBack) {
    var safeId = id.replace(/[\.\-]/g, "_");
    url = url + "?callback=callbacks." + safeId;
    var s = document.createElement("script");
    s.setAttribute("type", "text/javascript");
    s.setAttribute("src", url);

    callbacks[safeId] = function() {
        delete callbacks[safeId];
        var data = arguments[0];
        var node = document.getElementById(id);
        if (data && data.status == "200" && data.value) {
            renderCallBack(data, node);
        }
        else {
            data.value = "(error)";
            renderCallBack(data, node);
        }

        document.body.removeChild(s);
    };

    document.body.appendChild(s);
}

本质上,我将goJSONP和getDataForUrl压缩成一个函数,该函数编写脚本标记(并稍后删除它),并且不使用“unshift”函数,因为似乎会从args数组中删除服务器的响应。因此,我只是提取数据并使用可用参数调用我的回调。这里的另一个区别是,我重复使用回调名称,我可能会更改为具有计数器的完全唯一名称。

目前缺少的是超时处理。我可能会启动一个计时器并检查回调函数的存在。如果它存在,则尚未删除自身,因此它是超时,我可以相应地采取行动。


-1

这篇文章已经有一年的历史了,但我认为jfriend00的想法是正确的,虽然比那更简单 - 仍然使用闭包,只需在指定回调时添加参数即可:

http://url.to.some.service?callback=myFunc('optA')

http://url.to.some.service?callback=myFunc('optB')

然后使用闭包将其传递:

function myFunc (opt) {
    var myOpt = opt; // will be optA or optB

    return function (data) {
        if (opt == 'optA') {
            // do something with the data
        }
        else if (opt == 'optB') {
            // do something else with the data
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接