加载所有图像时解决承诺

Resolve promise when all images are loaded

我使用以下代码预加载图像:

    function preLoad() {
        var deferred = $q.defer();
        var imageArray = [];
        for (var i = 0; i < $scope.abbreviations.length; i++) {
            imageArray[i] = new Image();
            imageArray[i].src = $scope.abbreviations[i].imgPath;
        }
        imageArray.forEach.onload = function () {
            deferred.resolve();
            console.log('Resolved');
        }
        imageArray.forEach.onerror = function () {
            deferred.reject();
            console.log('Rejected')
        }
        return deferred.promise;
    }
    preLoad();

我认为图像都正确加载,因为我可以看到 'Resolved' 日志。

后来有人指出,上面的代码并不能保证在 resolve promise 之前加载所有图像。其实只解决了第一个promise。

有人建议我改用 $q.all 应用于承诺数组。 这是结果代码:

    function preLoad() {
        var imageArray = [];
        var promises;
        for (var i = 0; i < $scope.abbreviations.length; i++) {
            imageArray[i] = new Image();
            imageArray[i].src = $scope.abbreviations[i].imgPath;
        };

        function resolvePromises(n) {
            return $q.when(n);
        }
        promises = imageArray.map(resolvePromises);
        $q.all(promises).then(function (results) {
            console.log('array promises resolved with', results);
        });
    }
    preLoad();

这有效,但我想了解:

  1. 每个函数发生了什么;
  2. 为什么我需要 $q.all 来确保在解决承诺之前加载所有图像。

relevant docs 有点神秘。

不习惯angular promise库,但是思路如下:

function getImagePromise(imgData) {
    var imgEl = new Image();
    imgEl.src = imgData.imgPath;

    return $q(function(resolve, reject){
        imgEl.addEventListener('load', function(){
            if ((
                   'naturalHeight' in this 
                    && this.naturalHeight + this.naturalWidth === 0
                ) 
                || (this.width + this.height == 0)) {
                reject(new Error('Image not loaded:' + this.src));
            } else {
                resolve(this);
            }
        });

        imgEl.addEventListener('error', function(){
            reject(new Error('Image not loaded:' + this.src));
        });
    })
}

function preLoad() {
    return $q.all($scope.abbreviations.map(getImagePromise));
}

// using
preLoad().then(function(data){
    console.log("Loaded successfully");
    data.map(console.log, console);
}, function(reason){
    console.error("Error loading: " + reason);
});

看看这个 plunkr

你的函数:

function preLoad() {

    var promises = [];

    function loadImage(src) {
        return $q(function(resolve,reject) {
            var image = new Image();
            image.src = src;
            image.onload = function() {
              console.log("loaded image: "+src);
              resolve(image);
            };
            image.onerror = function(e) {
              reject(e);
            };
        })
    }

    $scope.images.forEach(function(src) {
      promises.push(loadImage(src));
    })

    return $q.all(promises).then(function(results) {
        console.log('promises array all resolved');
        $scope.results = results;
        return results;
    });
}

这个想法与 Henrique 的回答非常相似,但是 onload 处理程序用于解决每个承诺,而 onerror 用于拒绝每个承诺。

回答您的问题:

1) 承诺工厂

$q(function(resolve,reject) { ... })  

构造一个 Promise。传递给 resolve 函数的任何内容都将在 then 函数中使用。例如:

$q(function(resolve,reject) {
     if (Math.floor(Math.random() * 10) > 4) {
         resolve("success")
     }
     else {
         reject("failure")
     }
}.then(function wasResolved(result) {
    console.log(result) // "success"
}, function wasRejected(error) {
    console.log(error) // "failure"
})

2) $q.all 传递了一个承诺数组,then 接受一个函数,该函数传递一个数组,其中包含所有原始承诺的决议。