在JavaScript中,你能强制一个循环在当前迭代完成之前不处理下一次迭代吗?

In JavaScript, can you force a loop to not process the next iteration before the current iteration is complete?

我有一个数据集,其中包含 HTML5 canvas 的图层数组。我正在使用 fabric.js 来管理我的 canvas 的所有元素。这些图层应该按顺序加载,形成不同的场景。我通过 forEach 循环遍历数据集。 (编辑——经过进一步研究,我发现 for 和 forEach 循环都是同步的)。

问题是,一旦代码到达加载图像的位置,异步行为就会开始,进程会跳转到循环的下一次迭代以开始处理下一层。可以想象,这会导致图层在 canvas 上出现乱序并最终扭曲场景。

我已经为此工作并研究了数周,但运气不佳。

有什么方法可以防止 forEach 循环(或与此相关的 for 循环)在当前迭代完成之前不开始下一次迭代?

这是我的 forEach 循环代码:

$.ajax({
    url: url,
    method: "GET"
}).done(function (data) {
    data.forEach(function (data) {
        switch (data.layer_type) {
            case "texture":
                {
                    addTexture(data)
                    break;
                }
            case "color":
                {
                    addColor(data)
                    break;
                }
            case "room":
                {
                    addBaseImg(data);
                    break;
                }
            case "decor":
                {
                    addBaseImg(data);
                    break;
                }
            case "floor":
                {
                    addBaseImg(data);
                    break;
                }
            default:
                {
                    addOtherObjects(data);
                }
        }
    });
});

这里是一个加载图片的例子:

初始函数触发点击事件:

function addTexture(data) {
    $('img[data-obj-id="' + data.object_id + '"]').trigger("click");
}

导致此功能:

$(document).on("click", ".img-patt", function () {

    var imgURL = $(this).data("src");

    var _this = this;

    //this is where the process skips to the next iteration, before the image is loaded and added to the canvas

    fabric.Image.fromURL(imgURL, function (img) {

        //some code to handle the pattern removed from here just to shorten up the snippet

        var rect = new fabric.Rect({
            name: "texture",
            visible: true,
            selectable: false,
            data: {
                type: $(_this).data("type"),
                objid: $(_this).data("obj-id")
            },
            top: 0,
            left: 0,
            width: fabCanvas.width,
            height: fabCanvas.height,
            fill: pattern
        });

        fabCanvas.add(rect);
        fabCanvas.renderAll();
    });

});

你可能想使用一个技巧,你应该得到你想要的。

在开始 for 之前设置一个强制等待的变量。

现在我给你看一下你的情况

var wait = false;
$.ajax({
    url: url,
    method: "GET"
}).done(function (data) {
    wait = true;
    data.forEach(function (data) {
        switch (data.layer_type) {
            case "texture":
                {
                    addTexture(data)
                    break;
                }
            ...
            default:
                {
                    addOtherObjects(data);
                }
        }
    });
    wait=false;
});

并且在点击事件中你必须这样做

$(document).on("click", ".img-patt", function () {
    if (wait){
    // do samething
    }else{

    }
});

此外,如果你放一个计时器,功能会变得更可靠

function DoSamething(){
    if (wait){
        // do samething
    }else{
        window.setInterval(DoSamething, 1000);
    }
}

你的点击事件就这样

$(document).on("click", ".img-patt", function () {
     window.setInterval(DoSamething, 1000);
});

如果您想手动处理它而不是依赖某种 Promise 实现(例如 bluebird),您可以使用此逻辑。

整体看起来更复杂,但除了实际应该可行之外,当您看到并理解这一点时,添加您的应用知识,您将能够做一些更小、更好的事情。

这个概念不是在 canvas 上添加图像,而是创建一个已加载对象的有序数组,然后当您全部加载它们时,将该对象数组添加到 canvas。

你的点击变成:

function addToCanvas(object) {
  canvas.add(object);
}

$(document).on("click", ".img-patt", function () {

   var imgURL = $(this).data("src");

   addSomething(imgUrl, null, null, addToCanvas)

});

您的补充内容是:

function addSomething(imgUrl, index, objectStack, callback) {

        //this is where the process skips to the next iteration, before the image is loaded and added to the canvas

        fabric.Image.fromURL(imgURL, function (img) {

            //some code to handle the pattern removed from here just to shorten up the snippet

            var rect = new fabric.Rect({
                name: "texture",
                visible: true,
                selectable: false,
                data: {
                    type: $(_this).data("type"),
                    objid: $(_this).data("obj-id")
                },
                top: 0,
                left: 0,
                width: fabCanvas.width,
                height: fabCanvas.height,
                fill: pattern
            });

            objectStack && objectStack[index] = rect;
            callback(rect)
          });
}

你的主循环变成了:(for 循环和 forEach 都是 sincronous)

$.ajax({
    url: url,
    method: "GET"
}).done(function (data) {
    var objectStack = new Array(data.length);
    var loaded = data.length;
    var onFinish = function() {
      loaded--
      if (loaded === 0) {
        objectStack.forEach(function(object) {
          canvas.add(object);
        });
      }
    }
    data.forEach(function (data, index) {
        var src = $('img[data-obj-id="' + data.object_id + '"]').data('src');
        switch (data.layer_type) {
            case "texture":
                {
                    addSomething(src, index, objectStack, onFinish)
                    break;
                }
            case "color":
                {
                   ...
                }
            case "room":
                {
                    ...
                }
            case "decor":
                {
                   ...
                }
            case "floor":
                {
                    ...
                }
            default:
                {
                    ...
                }
        }
    });
});