不断迭代promises会导致堆栈过深

时间:2015-05-30 06:45:43

标签: javascript promise circular-list

稍后编辑

这个问题应该删除,因为我报告的问题甚至不存在。

没有'长链关闭',这是我误解了Google Chrome观看窗口。

视频元素未正确清理可能导致内存泄漏。这是一个不同的问题,已在其他问题中得到解决。

<小时/> 我正在编写Javascript代码以不断循环播放列表。播放列表中的项目是图像(显示10秒钟)或视频。

这里有一些代码(处理图片或视频)

var IMAGE = 0;
var VIDEO = 1;

var mediaElements = [{
    url: 'https://upload.wikimedia.org/wikipedia/commons/thumb/b/bd/Rembrandt_van_Rijn_-_Self-Portrait_-_Google_Art_Project.jpg/180px-Rembrandt_van_Rijn_-_Self-Portrait_-_Google_Art_Project.jpg',
    mediaType: IMAGE
}, {
    url: 'https://upload.wikimedia.org/wikipedia/commons/thumb/f/f7/English_Pok%C3%A9mon_logo.svg/269px-English_Pok%C3%A9mon_logo.svg.png',
    mediaType: IMAGE
}, {
    url: 'http://www.w3schools.com/html/mov_bbb.mp4',
    mediaType: VIDEO
}, {
    url: 'https://upload.wikimedia.org/wikipedia/en/thumb/3/34/Teuchitlan_scale_model_1_cropped.jpg/133px-Teuchitlan_scale_model_1_cropped.jpg',
    mediaType: IMAGE
}, {
    url:
        'https://upload.wikimedia.org/wikipedia/en/f/f7/Sugimoris025.png',
    mediaType: IMAGE
}


];

$(function () {

    function displayMediaFile(mediaFile) {
        $('#imageTarget').empty();
        if (mediaFile.mediaType === IMAGE) {
            $('#imageTarget').append($('<img />').attr('src', mediaFile.url));
            return new Promise(function (resolve, reject) {
                window.setTimeout(resolve, 2000);
            });
        } else {
            var videoElement = $('<video></video>').attr('autoplay', '');
            var sourceElement = $('<source><source>')
                .attr('src', mediaFile.url)
                .attr('type', 'video/mp4')
                .appendTo(videoElement);
            videoElement = videoElement.appendTo('#imageTarget');
            return new Promise(function (resolve, reject) {
                videoElement[0].onended = resolve;
            });
        }
    };


    function iterateThroughPlaylist(index) {
        if (index >= mediaElements.length) {
            index = 0;
        }
        console.log('displaying image ' + index);
        displayMediaFile(mediaElements[index]).then(function () {
            iterateThroughPlaylist(index + 1);
        });
    }

    iterateThroughPlaylist(0);
});

有关完整的工作示例,请参阅此jsfiddle

所以实际的'displayMediaFile&#39;函数返回一个Promise。这个承诺在游戏完成后解决,然后我们继续下一个图像。

问题是,它会在一段时间后停止运行。我在iterateThroughImages方法中放了一个断点,然后查看了调用堆栈。我可以看到一个非常长的闭合链。

我能否以相同的简单性生成代码,但不知何故避免让运行时将闭包保留在内存中?

2 个答案:

答案 0 :(得分:2)

承诺似乎不是最好的解决方案。

var imageUrls = [ url1, url2, url3, url4  ];
(function play(index){
    console.log('displaying image ' + index);
    var url = imageUrls[index];
    $('#imageTarget').empty().append($('<img />').attr('src', url));
    window.setTimeout(play, 2000, (index+1)%imageUrls.length);
})(0);

如果你有其他的连锁操作但是没有构建无限的承诺链,你仍然可以在里面使用promises。

附注:您不必删除并重新创建img元素,只需更改其src属性即可。

答案 1 :(得分:0)

这是承诺不是最佳解决方案的情况。由于长期稳定性是一个问题,因此最好让iterateThroughPlaylist更直接地再次运行:

  • image:window.setTimeout(iterateThroughPlaylist, 2000);
  • 视频:videoElement[0].onended = iterateThroughPlaylist;

与承诺一样,没有递归,因为在两种情况下都会在稍后的事件转换中调用iterateThroughPlaylist

$(function () {
    var mediaTypes = { 'IMAGE':0, 'VIDEO':1 },
        mediaElements = [
            ...
            ...
            ...
        ],
        index = -1;
    function displayMediaFile(index, fn) {
        var mediaFile = mediaElements[index];
        $('#imageTarget').empty();
        switch(mediaFile.mediaType) {
            case mediaTypes.IMAGE:
                $('#imageTarget').append($('<img/>').attr('src', mediaFile.url));
                window.setTimeout(fn, 2000);
            break;
            case mediaTypes.VIDEO:
                var videoElement = $('<video/>')
                    .attr('autoplay', '')
                    .append($('<source/>').attr({ 'src': mediaFile.url, 'type': 'video/mp4')})
                    .appendTo('#imageTarget');
                videoElement[0].onended = fn;
            break;
            default:
                //do nothing and let the sequence lapse, or
                //eg. window.setTimeout(fn, 2000); to keep trying
        }
    };
    function iterateThroughPlaylist() {
        index = (index + 1) % mediaElements.length;
        displayMediaFile(index, arguments.callee);
    }
    iterateThroughPlaylist();
});

备注

  • arguments.callee作为回调传递只是一种不对函数名称进行硬编码的方法iterateThroughPlaylist
  • displayMediaFileiterateThroughPlaylist中没有内部函数,因此只有最外层的$(function () {...}形成了预期的闭包。
  • 包含$(function () {...}结构中的所有内容,无需使用全局命名空间。
  • switch/case可以在必要时更轻松地添加其他媒体类型。
  • 为了提高效率,您可以预先创建<img><video>元素并重复使用。