从具有嵌套ajax调用的函数返回一个对象

时间:2013-05-31 12:50:31

标签: javascript jquery ajax jquery-deferred

我想编写一个从youtube视频返回信息的javascript函数;更具体一点,我想在json对象中获取搜索获得的ID和视频长度。所以我看了一下youtube API,然后我推出了这个解决方案:

   function getYoutubeDurationMap( query ){
        var youtubeSearchReq = "https://gdata.youtube.com/feeds/api/videos?q="+ query +
                "&max-results=20&duration=long&category=film&alt=json&v=2";
        var youtubeMap = [];
        $.getJSON(youtubeSearchReq, function(youtubeResult){
            var youtubeVideoDetailReq = "https://gdata.youtube.com/feeds/api/videos/";
            for(var i =0;i<youtubeResult.feed.entry.length;i++){
                var youtubeVideoId = youtubeResult.feed.entry[i].id.$t.substring(27);
                $.getJSON(youtubeVideoDetailReq + youtubeVideoId + "?alt=json&v=2",function(videoDetails){
                    youtubeMap.push({id: videoDetails.entry.id.$t.substring(27),runtime: videoDetails.entry.media$group.media$content[0].duration});

                });
            }
        });
        return youtubeMap;    

    }

逻辑是正确的,但是当我调用这个函数时,你们很多人已经理解了因为ajax而得到一个空数组。反正有没有得到完整的对象?我应该使用Deferred对象吗?谢谢你的回答。

2 个答案:

答案 0 :(得分:4)

是的,您应该使用延迟对象。

这里最简单的方法是创建一个数组,您可以在其中存储内部jqXHR调用的$.getJSON()结果。

var def = [];
for (var i = 0; ...) {
    def[i] = $.getJSON(...).done(function(videoDetails) {
        ... // extract and store in youtubeMap
    });
}

然后在整个函数结束时,使用$.when创建一个新的promise,只有当所有内部调用完成时才会解析:

return $.when.apply($, def).then(function() {
    return youtubeMap;
});

然后使用.done来处理函数的结果:

getYoutubeDurationMap(query).done(function(map) {
    // map contains your results
});

有关使用此YouTube API的演示,请参阅http://jsfiddle.net/alnitak/8XQ4H/,了解延迟对象如何允许您将“AJAX”调用与“持续时间搜索”的后续数据处理完全分开。

代码有点长,但也在这里复制。但是,虽然代码比您预期的要长,但请注意,此处的通用功能现在可以重复用于您可能要对YouTube API进行的任何调用。

// generic search - some of the fields could be parameterised
function youtubeSearch(query) {
    var url = 'https://gdata.youtube.com/feeds/api/videos';
    return $.getJSON(url, {
        q: query,
        'max-results': 20,
        duration: 'long', category: 'film',  // parameters?
        alt: 'json', v: 2
    });
}

// get details for one YouTube vid
function youtubeDetails(id) {
    var url = 'https://gdata.youtube.com/feeds/api/videos/' + id;
    return $.getJSON(url, {
        alt: 'json', v: 2
    });
}

// get the details for *all* the vids returned by a search
function youtubeResultDetails(result) {
    var details = [];

    var def = result.feed.entry.map(function(entry, i) {
        var id = entry.id.$t.substring(27);
        return youtubeDetails(id).done(function(data) {
            details[i] = data;
        });
    });

    return $.when.apply($, def).then(function() {
        return details;
    });
}

// use deferred composition to do a search and then get all details
function youtubeSearchDetails(query) {
   return youtubeSearch(query).then(youtubeResultDetails);
}

// this code (and _only_ this code) specific to your requirement to
// return an array of {id, duration}
function youtubeDetailsToDurationMap(details) {
    return details.map(function(detail) {
        return {
            id: detail.entry.id.$t.substring(27),
            duration: detail.entry.media$group.media$content[0].duration
        }
    });
}

// and calling it all together
youtubeSearchDetails("after earth").then(youtubeDetailsToDurationMap).done(function(map) {
    // use map[i].id and .duration
});

答案 1 :(得分:0)

正如您所发现的那样,您无法直接返回youtubeMap,因为它尚未在返回时填充。但是,您可以返回已完全填充的youtubeMap承诺,可以对其进行操作,例如.done(), .fail().then()

function getYoutubeDurationMap(query) {
    var youtubeSearchReq = "https://gdata.youtube.com/feeds/api/videos?q=" + query + "&max-results=20&duration=long&category=film&alt=json&v=2";
    var youtubeVideoDetailReq = "https://gdata.youtube.com/feeds/api/videos/";
    var youtubeMap = [];
    var dfrd = $.Deferred();
    var p = $.getJSON(youtubeSearchReq).done(function(youtubeResult) {
        $.each(youtubeResult.feed.entry, function(i, entry) {
            var youtubeVideoId = entry.id.$t.substring(27);
            //Build a .then() chain to perform sequential queries
            p = p.then(function() {
                return $.getJSON(youtubeVideoDetailReq + youtubeVideoId + "?alt=json&v=2").done(function(videoDetails) {
                    youtubeMap.push({
                        id: videoDetails.entry.id.$t.substring(27),
                        runtime: videoDetails.entry.media$group.media$content[0].duration
                    });
                });
            });
        });
        //Add a terminal .then() to resolve dfrd when all video queries are complete.
        p.then(function() {
            dfrd.resolve(query, youtubeMap);
        });
    });
    return dfrd.promise();
}

getYoutubeDurationMap()的调用将采用以下形式:

getYoutubeDurationMap("....").done(function(query, map) {
    alert("Query: " + query + "\nYouTube videos found: " + map.length);
});

注意:

  • 在实践中,您可能会遍历map并显示.id.runtime数据。
  • 顺序查询优于并行查询,因为顺序对客户端和服务器都更友好,并且更有可能成功。
  • 另一个有效的方法是返回一组单独的promises(每个视频一个)并使用$.when.apply(..)响应完成,但是提取所需的数据会更加笨拙。