美文网首页
JavaScript同步执行阻塞UI线程渲染的解决方案

JavaScript同步执行阻塞UI线程渲染的解决方案

作者: 赵一鸣的笔记本 | 来源:发表于2018-01-01 18:50 被阅读0次

每到周末就喜欢自己折腾demo,早就注册了简书,但一直没时间在上面写东西,今天正好遇到点问题,同步在这里记录下。

在之前的项目VueNode中有一个优惠券列表页,当时使用点击按钮加载更多的方式实现的,但是这里有个问题:如果用户网络环境不好,或者正好当时并发太大,即使是每次只加载10条数据,依然会出现延迟,这个时候在页面中加一个loading效果,提示用户数据正在加载,体验会更好些,于是就先着手写一些demo,本以为只是一个loading图片定位布局外加蒙层的效果,没想到却是一波三折。。。

目使用Vue开发,所有的数据请求都是基于VueResource的(其实就是类似jQuery的ajax异步),本来是想提高代码重用性,封装一个getData的函数,这样就省下每次写一些结构性的代码,只需要传入参数,然后return返回结果即可,测试代码如下:

function getData () {
    var res;
    $.ajax({
        type: 'get',
        async: false, // 因为要return res,所以这里使用同步
        url: './test.php?a=1&b=2',
        dataType: 'json',
        success: function (data) {
            res = data;
        }
    });
    return res;
}

为了模拟数据加载慢的场景,PHP输出延迟了2s:

sleep(2);
echo json_encode($_GET);

最后执行点击事件:

$('#btn').click(function () {
    $('#loading').show();
    console.log(getData());
    $('#loading').hide();
});

本以为这样就可以了,但是在浏览器里执行的时候,loading图根本没有出现,只是过了2s在控制台打印出了输出结果,郁闷。。。

然后仔细看了下代码,初步判定是JS线程阻塞了UI线程,所以loading图无法被渲染到浏览器中显示,这也是我们平时将JS脚本放到页面底部,并且在页面加载过程中尽量不操作DOM的原因。

我的初始目的是封装一个公用函数去加载数据,但是鉴于同步加载可能导致的上述问题,只能放弃改用异步。但是异步加载又可能会导致另外的问题:回调地狱,解决当前的需求,只需一次回调,问题不大,但是后期需要通过异步加载很多数据,并且每一次异步加载都需要上一次加载的结果作为条件,就不好办了,如下代码:

setTimeout(function () {
    // do something
    setTimeout(function () {
        // do something
        setTimeout(function () {
            // do something
            // ... 无限回调
        });
    });
});

网上找了下资料,看看jQuery解决多次异步回调的方法,还真有:jQuery在1.5版本之后,引入了Deferred对象,提供的很方便的异步机制,所以,整理以上代码如下:

function getData () {
    var defer = $.Deferred();
    $.ajax({
        type: 'get',
        async: true,
        url: './test.php?a=1&b=2',
        dataType: 'json',
        success: function (data) {
            defer.resolve(data);
        }
    });
    return defer.promise();
}

$('#btn').click(function () {
    $('#loading').show();
    $.when(getData()).done(function (data) {
        console.log(data);
        $('#loading').hide();
    });
    
});

defer.resolve(data),Deferred对象的resolve方法传入一个任意类型的参数,并且这个参数可以在done方法中拿到,最后我们异步请求来的数据就可以正常返回了,而且不会阻塞UI线程。

本来问题到此为止算是解决了,但是忽然想到:现在的异步代码是运行在浏览器,主要是操作DOM,所以有jQuery帮忙解决多层异步回调,如果是运行在服务端的Node,肿么办,毕竟这种场景很常见。

ES6提供了异步对象Promise,其中Promise.all()就是来解决多层回调问题的,之前我在VueNode项目中获取首页数据就用到了这种方法,代码片段如下:

let bannerData = new Promise((resolve, reject) => {
    // do something
    resolve(data);  
});

let hotCoupon = new Promise((resolve, reject) => {
    // do something
    resolve(data);
});

Promise.all([bannerData, hotCoupon]).then((res) => {
    // do something
});

甚至我们可以用ES7新推出的async/await这种方法,代码更直观。

不管是jQuery的Deferred还是ES6的Promise,都是这样一个过程:先让某一段代码执行着,等有结果了再来通知我,即使永远没有结果,也不要紧,不会阻塞其他代码的执行。这个不是很像发布订阅模式吗?Vue中的$emit、$on就是发布订阅,主要解决多个组件之间数据交互的。所以来测试下,还是实现以上的loading效果:

// 发布订阅类
class PubSub {
  constructor () {
    this.eventList = {};
  }

  on (eventName, callback) {
    if (this.eventList[eventName] === undefined) {
      this.eventList[eventName] = [];
    }
    this.eventList[eventName].push(callback);
  }

  emit (eventName, ...args) {
    if (this.eventList[eventName] === undefined) {
      return false;
    }
    this.eventList[eventName].forEach((item) => {
      item.apply(null, args);
    });
  }
}

const pubsub = new PubSub();

pubsub.on('loading', (...args) => {
    console.log(args);
    $('#loading').hide();
});

$('#btn').click(function () {
    $('#loading').show();

    $.ajax({
        type: 'get',
        async: true,
        url: './test.php?a=1&b=2',
        dataType: 'json',
        success: function (data) {
            pubsub.emit('loading', data);
        }
    });
});

试着运行,果然可以。

总结:
(1)Ajax默认是异步的,所以最好不要强制使用同步,即使涉及到多层嵌套,也有很多方案可以解决;
(2)使用JS操作DOM的时候要时刻注意JS会阻塞UI渲染,即使要操作DOM,也要确保其他UI渲染操作没有同时执行;
(3)如果习惯了异步,真的是很好用,Node很流行应该也有这方面的原因吧。

相关文章

  • JavaScript同步执行阻塞UI线程渲染的解决方案

    每到周末就喜欢自己折腾demo,早就注册了简书,但一直没时间在上面写东西,今天正好遇到点问题,同步在这里记录下。 ...

  • 《深入浅出NodeJS》读书笔记:Node的异步I/O

    为什么要异步I/O? 消除UI阻塞,快速响应资源 JavaScript是单线程的,它与UI渲染共用一个线程。所以在...

  • IO/NIO、阻塞/非阻塞、同步/异步

    同步与异步 同步与异步是对线程来说的,是指一个任务在同一个线程执行还是多个线程合作执行。 阻塞与非阻塞 阻塞和非阻...

  • Swift GCD 的串行队列与并行队列

    队列异步是否阻塞当前线程同步是否阻塞当前线程执行顺序串行队列否是按添加顺序并行队列否是同时执行,但会被同步阻塞 串...

  • 多线程实测01

    1.主线程队列中实测。 a.主线程队列中不能执行同步,线程阻塞。代码中有异步和同步两个执行操作,当同步执行在前时,...

  • 同步、异步与串行、并发

    1、同步、异步描述的是当前线程或代码流是否要阻塞以等待加入队列的任务执行完毕;同步要阻塞当前线程,异步不会阻塞当前...

  • GCD 串行队列并行队列和同步派发异步派发

    串行队列 异步派发 不会造成线程阻塞,但是会依次执行派发任务 同步派发 依次执行派发任务,会阻塞线程 先异步派发再...

  • 腾讯面经

    阻塞、非阻塞、同步、异步 的区别 阻塞阻塞调用是指调用结果返回之前,当前线程会被挂起(线程进入非可执行状态,在这个...

  • 01. 多线程Task做延迟操作

    1.异步执行,不会阻塞当前线程 2. 同步等待,当前线程会等三秒完后继续往下执行 3. 异步,不阻塞当前线程的执行...

  • GCD使用详解(swift版)

    一、概念解释 1、同步 :阻塞主线程,等执行完才能返回主线程。2、异步:不阻塞主线程,提交线程后立马返回。3、串行...

网友评论

      本文标题:JavaScript同步执行阻塞UI线程渲染的解决方案

      本文链接:https://www.haomeiwen.com/subject/ojwtnxtx.html