首页 > 解决方案 > .then() 在循环期间未多次执行内部函数

问题描述

我正在编写一段实现承诺的代码。它在一个循环中,并迭代输入到页面中的图像数量。首先它应该用新图像更新画布,然后它应该运行一些代码来从画布图像中获取信息。这是我目前的功能。

这是将图像更改为输入中的下一个的原因。

function readNextImage(i) {
  d = new $.Deferred();
  if (fileUpload.files && fileUpload.files[i]) {
    var FR = new FileReader();
    FR.onload = function(e) {
      fabric.Image.fromURL(e.target.result, function(img) {
        img.set({
          left: 0,
          top: 0,
          evented: false
        });
        img.scaleToWidth(canvas.width);
        img.setCoords();
        canvas.add(img);
      })
      var imgData = context.getImageData(0,0,canvas.width,canvas.height);
      d.resolve(imgData);
    };
    FR.readAsDataURL(fileUpload.files[i]);
  }
  return d.promise();
}

这是 .then() 命令中调用的另一个函数的片段。

function read_image (imgData) {
    console.log("in read_image");
    data = imgData.data;
    /*doing some stuff to the data object*/ 
    console.log("here is some data output");
}

最后这里是调用这两个函数的地方

for (var image_num=0; image_num<fileUpload.files.length; image_num+=1){
    console.log("loop:"+image_num);
    promise = readNextImage(image_num).then(read_image);    
}

因此,正在发生的事情是画布正在使用新图像进行更新,但只有在最终图像更新到画布后,当我从read_image()函数中看到任何 console.logs 时,我才看到一个输出。这是控制台输出的示例。

loop:0
loop:1
loop:2
loop:3
loop:4
loop:5
loop:6
loop:8
in read_image
here is some data output

任何关于为什么 then() 内部的函数只被调用一次的见解将不胜感激。

标签: javascriptjquerycanvaspromise

解决方案


您需要使用,或声明d为函数的本地。您可能会覆盖更高范围的内容,然后解决错误的内容。letconstvard

function readNextImage(i) {
  var d = new $.Deferred();
//^^^

其他问题:

  1. FileReader 对象没有错误处理,因此任何错误都会导致您的承诺永远不会解决或拒绝。
  2. 在解决您的承诺之前,您无需等待fabric.Image.fromURL()(可能是异步的)完成。
  3. 如果您if在这里的陈述不满意if (fileUpload.files && fileUpload.files[i]),那么您将永远不会解决或拒绝承诺。

这是清理这四个问题的尝试(无法测试代码以确保它完全正确):

function readNextImage(i) {
    var d = new $.Deferred();
    if (fileUpload.files && fileUpload.files[i]) {
        var FR = new FileReader();
        FR.onload = function(e) {
            fabric.Image.fromURL(e.target.result, function(img) {
                img.set({
                    left: 0,
                    top: 0,
                    evented: false
                });
                img.scaleToWidth(canvas.width);
                img.setCoords();
                canvas.add(img);
                var imgData = context.getImageData(0, 0, canvas.width, canvas.height);
                d.resolve(imgData);
            });
        };
        FR.onerror = function(e) {
            d.reject(e);
        }
        // start the file reader
        FR.readAsDataURL(fileUpload.files[i]);
    } else {
        // not sure what you want to do here, but you need to resolve or reject the promise
        d.resolve()
    }
    return d.promise();
}

在编写具有多个尚未处理承诺的异步操作的代码时,通常最好在最低级别“承诺”每个异步操作。这样,您就可以使用 Promise 的排序和流控制以及错误处理来一致地编写所有程序逻辑(这是一个很大的优势)。在这种情况下,您可以这样做:

function readFileDataURL(f) {
    return new Promise(function(resolve, reject) {
        var fr = new FileReader();
        fr.onload = resolve;
        fr.onerror = reject;
        fr.readAsDataURL(f);
    });
}

function imageFromURL(data) {
    return new Promise(function(resolve, reject) {
        fabric.Image.fromURL(data, resolve)
    });
}

function readNextImage(i) {
    if (fileUpload.files && fileUpload.files[i]) {
        return readFileDataURL(fileUpload.files[i]).then(function(e) {
            return imageFromURL(e.target.result);
        }).then(function(img) {
            img.set({
                left: 0,
                top: 0,
                evented: false
            });
            img.scaleToWidth(canvas.width);
            img.setCoords();
            canvas.add(img);
            var imgData = context.getImageData(0,0,canvas.width,canvas.height);
            return imgData;
        });
    } else {
        // nothing to do here
        return Promise.resolve();
    }
}

请注意,您还获得了两个实用函数,它们在您可能可以在其他地方重用的 Promise 中工作。


推荐阅读