返回

JS循环后代码执行问题?2招搞定异步!

javascript

解决循环未结束时的最后一行执行问题

程序设计中,经常会遇到这样的场景:一个耗时的循环操作完成后,需要执行后续的代码。但由于 JavaScript 的单线程特性,代码可能会提前执行到循环后的语句,造成逻辑错误。以 jQuery 的例子说明这个问题:在点击按钮时显示加载动画,执行一个循环,循环结束后隐藏加载动画,但在实际中动画经常会提前隐藏,无法按预期工作。本文探讨产生这种问题的原因并提供解决方案。

问题分析

这段代码问题核心在于 JavaScript 的执行机制。尽管循环是在按钮点击事件中触发,但循环体内的操作并未阻塞 JavaScript 线程。这意味着当执行到隐藏动画的代码时,循环可能还没有完成。代码的执行是按照代码出现的顺序来的,尽管我们期望先循环,再隐藏,实际并非如此。

解决方案

核心目标是在循环执行结束后才执行后续的代码,这可以从两种角度来解决:利用异步机制或者避免长时间的阻塞。

1. 拆分循环使用setTimeout模拟异步

这个方法是通过拆分循环并利用 setTimeout 来模拟异步执行效果。 每次执行一小部分循环,并延迟执行下一次,使得隐藏动画有机会在真正完成循环时执行。这可以确保其他UI更新有机会进行,用户不会感受到界面卡顿。

代码示例:

$('#ajaxSpinnerImage').hide();

$('#bulkUploadButton').on('click',function(){
    $('#ajaxSpinnerImage').show();
    var i = 0;
    var chunkSize = 100; // 每次执行的步长
    var arrayLength = casNumbersArray.length;


    function processChunk() {
        for (var j = 0; j < chunkSize && i < arrayLength; j++, i++) {
            // 长时间运行的操作
        }
      if (i < arrayLength) {
          setTimeout(processChunk, 0); // 继续执行下一个片段,利用延迟机制。
      }else {
        $('#ajaxSpinnerImage').hide(); //循环结束,隐藏动画。
      }
    }

   processChunk();
});

操作步骤:

  1. 定义chunkSize确定每次处理循环体的量,平衡性能和页面渲染速度。
  2. 使用递归函数processChunk 处理一部分数据,并利用 setTimeout 让浏览器有机会渲染。
  3. processChunk中,每次增加索引并检查是否达到数组结尾,没有就设置另一个延时执行,到达结尾隐藏加载。
    安全提示: 循环步长的调整应根据具体操作耗时和页面响应性能要求来决定。

2. 使用async/await (ES8特性)

假如在循环中调用的是异步操作,async/await 将使得异步操作的同步化更加清晰。 async 函数会返回 Promise 对象, await 可以等待 Promise resolve ,保证执行的顺序。 配合forEach/map可以更简单的管理循环。

代码示例:

$('#ajaxSpinnerImage').hide();

$('#bulkUploadButton').on('click',async function(){
    $('#ajaxSpinnerImage').show();

    //假如循环体中有异步操作, 此处只是演示,实际的场景根据自己的异步操作实现修改
    async function performOperation(item){
        return new Promise(resolve=>{
            setTimeout(() => {
               //异步操作
                resolve(item)
            }, 50);
        })
    }
    try {
         for(const item of casNumbersArray){
               await performOperation(item) // 等待异步操作完成
         }

    }
     catch(e) {
          // 异步出错处理

     }finally {
          $('#ajaxSpinnerImage').hide();  //循环结束,隐藏动画。
    }

});

操作步骤:

  1. 将整个 click 事件处理函数标记为 async
  2. 将循环体中的操作,封装到一个 async 函数中。
  3. 使用 await 等待 async 函数的执行结果,这样可以控制异步操作的执行顺序。

安全提示: async/await 要求循环体内操作为 Promise 对象。需要为await 加上错误处理。

结论

当需要在耗时的循环后执行特定的代码,传统的同步方法会存在执行顺序错误的问题。利用 setTimeout 模拟异步或者使用 async/await 是两个可靠的选择。选择哪个方案取决于实际代码逻辑:循环内部是否需要异步操作,或是否存在阻塞界面的风险。合理使用这些方法能保证代码执行逻辑正确,提升用户体验。