函数内的ajax同步请求导致遮罩层失效、或者导致loading正在加载提示失效问题

功能需求

编写点击按钮,弹出loading遮罩层提示正在加载中…,同时查询后台信息,拼接数据后渲染到列表时,为了保障渲染时,列表中的数据已经拼接完成。所以在for循环中使用同步ajax进行请求,在success方法中进行拼接。

function showPrintJobList(){
	var loadingTip = $('#loading');
	loadingTip.show();
	for(var i = 0;i < rwid.length;i++){
		$.ajax({
			url: xxxxx,
			async: false,  //默认异步,为保证数据完整和顺序使用同步请求
			type: 'POST',
			data:{
				xxx: xxx
			},
			dataType: 'json',
	      	success: function(result) {
				//.....循环拼接结果,弹窗显示列表:$('#rwlist').show();
			},
			error: function(err){
				console.log(err);
			}
		});
	}
	loadingTip.hide();
	$('#rwlist').show();
}

问题信息

点击按钮后,遮罩层没有弹出,而是等待表单渲染完成后,看到一闪而过的遮罩层。

问题原因async: false

ajax同步请求在运行的时候,浏览器的UI、Dom元素渲染会被阻塞。所以当点击事件触发showPrintJobList函数时,loading.show()是有被加载到浏览器的js运行线程中,但是运行到ajax同步请求时,会将loading.show()阻塞执行,等到同步ajax请求完了,才会运行。然后同步ajax已经请求完了,然后直接被隐藏,然后显示列表。所以会看到一闪而过的loading遮罩层

解决方法

一、使用async wait关键字
二、使用JQuery的Deferred对象和$.when().done().fail()

async/await关键字
  1. 标识所修饰的函数存在异步代码,函数内部可用await关键字修饰异步过程变为同步。函数内部也可以全部为同步代码。
  2. 非阻塞:async所标识的函数内存在异步代码,会等待异步代码执行完,但是async函数本身会马上返回,不会阻塞当前js工作主线程,可以简单认为,async函数工作在主线程,同步执行,不会阻塞界面渲染,async函数内部由await关键字修饰的异步过程,工作在相应的协程上,会阻塞等待异步任务的完成再返回;

代码修改如下:

async function showPrintJobList(){
	var loadingTip = $('#loading');
	loadingTip.show();
	for(var i = 0;i < rwid.length;i++){
		await $.ajax({   //使用await关键字修饰异步请求,保证数据顺序完整
			url: xxxxx,
			async: true,  //有await关键字,ajax请求修改为异步
			type: 'POST',
			data:{
				xxx: xxx
			},
			dataType: 'json',
	      	success: function(result) {
				//.....循环拼接结果,弹窗显示列表:$('#rwlist').show();
			},
			error: function(err){
				console.log(err);
			}
		});
	}
	loadingTip.hide();
	$('#rwlist').show();
}

或者使用JQuery的Deferred对象和$.when().done().fail()使异步请求同步执行,同时不阻塞主线程。

var dataList = [];
function showPrintJobList(){
	var defer = $.Deferred();
	for(var i = 0;i < rwid.length;i++){
		$.ajax({   
			url: xxxxx,
			async: true,  //使用Deferred对象,ajax请求修改为异步
			type: 'POST',
			data:{
				xxx: xxx
			},
			dataType: 'json',
	      	success: function(result) {
				//dataList保存结果
				dataList.push(result);
				defer.resolve(dataList);
			},
			error: function(err){
				defer.reject(err);
			}
		});
	}
	return defer;
}

$('button#showList').click(function(){
	var loadingTip = $('#loading');
	loadingTip.show();
	$.when(showPrintJobList()).done(function(data){
        //循环拼接data中的list中的数据
        loadingTip.hide();
		$('#rwlist').show();
    }).fail(function(){
    	console.log(err);
    });
}

原理解析

浏览器中一般会运行UI线程、JS线程以及事件触发线程。
在上述上述代码中,loading.show()方法涉及到dom树的更新,所以被加载到了UI线程执行任务中。但是后面的ajax同步请求,因为不是异步的所以浏览器不会重新新增一个线程进行请求,而是直接放在js线程中阻塞其他线程执行。所以UI线程不能执行loading.show()方法。


版权声明:本文为good_good_xiu原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。