ajax的原函数,如何实现异步代码用同步方式写(Web端)?

本文探讨了JavaScript中处理异步操作的问题,尤其是回调函数导致的回调金字塔。通过示例展示了如何使用$.ajax和setTimeout,然后提出了一种使用函数包裹和递归实现的方式,将异步代码模拟成同步执行。虽然这种方式存在一定的局限性,但为了解决回调地狱提供了一个思路,特别提到了ES6和ES7的Promise作为更现代的解决方案。
摘要由CSDN通过智能技术生成

前端写JavaScript的时候,经常需要使用两个异步回调的方法:$.ajax和setTimeout,这两个都是需要异步方式执行回调($.ajax可以设置为同步,但会卡住浏览器,不推荐这么使用),当有好多个$.ajax的时候,就会出现好多个回调,写起来很麻烦,甚至会出现传说中的回调金字塔,就像叠罗汉那样,Javascript有办法像Java、C#后台语言那样用同步方式写代码么?很抱歉,原生是不支持的(ES6、7会支持Promise同步调用,但需要非常高级的浏览器才支持,即使Node也不完全支持)

一般怎么写?

var start = new Date();

//普通jsonp调用

$.ajax({

type: "get",

url: "http://tc.netease.com/zxz/datetime.php",

dataType: "jsonp",

success: function(__data){

//成功后打印时间

console.log(__data);

//相隔2秒后再次打印

setTimeout(function(){

console.log(new Date()-start);

},2000);

}

});

上面是比较普遍的写法,$.ajax调用接口后,输出接口值,然后在相隔2秒后,输出时间,要是再多几个,代码就很难想象了,而且经常会有童鞋直接再调用完$.ajax就想得到接口的值,明显是不行的

可不可这样实现?

var start = new Date();

//调用接口,获取返回值

var time = $.ajax("http://tc.netease.com/zxz/datetime.php");

console.log(time);

//停顿两秒

setTimeout(2000);

//再输出时间

console.log(new Date()-start);

这里的代码,相比上面的,要清爽很多,简单、直接、明了,而且不容易犯异步请求获取数据的错。

但显然,原生这么写,是不行的。换个思路实现呢?比如这样的:

define(function(){

var start = new Date();

var time = $.ajax("http://tc.netease.com/zxz/datetime.php");

console.log(time);

setTimeout(2000);

console.log(new Date()-start);

});

用一个函数包裹着需要同步跑的代码(实际还是异步),退一步这样实现的话,貌似还能接受

实现方式

实现方式,类似于ES6、ES7的Babel转换为ES5方式的代码,这里是将函数用字符串拼接,最终使用eval来执行,而内部是用递归来实现,以下为简单实现方式,有点简单粗暴,使用上需要自行注意处理细节问题。

1、定义一个define函数,参数是函数引用,就像这样的:

function define(func){}

2、去掉传进来函数的前缀function(){和尾部的}

//简单粗暴的使用字符串位置来去掉

funcStr = funcStr.substring(12,funcStr.length-1);

3、获取所有var的变量定义,以及将他们都置顶

//置顶所有变量定义,删除所有原函数中的所有var

var varList = funcStr.match(/var\s([0-9a-zA-Z_\$]+)[,;\s]/gi);

evalFunc += varList.join(";") + ";";

//把var都去掉

funcStr = funcStr.replace(/var\s/gi,"");

4、切分代码

将$.ajax和setTimeout为分割点,将函数代码切分为对应数量*2

//切分成多个模块

//模块编号

var switchIndex = 1;

//上一个模块的字符串位置

var lastProcessIndex = 0;

//正则匹配,切分代码

funcStr.replace(/(\w+\s=\s\$.ajax\([^\)]+\))|(setTimeout\(\d+\))/gi,function($a,$b){

//拼接出代码分隔符前面的代码

evalFunc += funcStr.substring(lastProcessIndex, funcStr.indexOf($a));

switchIndex +=1;

//拼接ajax或者setTimeout代码

evalFunc += ($a.indexOf("$.ajax") > -1 ? processAjax($a,switchIndex) : processSetTimeout($a,switchIndex));

switchIndex +=1;

lastProcessIndex = funcStr.indexOf($a) + $a.length + 1;

});

5、实现$.ajax和setTimeout的逻辑

function processAjax(code,index){

//抽取请求地址

var url = code.match(/"([^"]+)"/i)[1];

//抽取出返回值赋值的变量名

var param = code.match(/(\w+)\s=\s/i)[1];

var ret = 'case '+index + ':';

ret += '$.ajax({\

type: "get",\

url: "'+url+'",\

dataType: "jsonp",\

success: function(__data){\

'+param+'=__data;\

process('+(index+1)+');\

}\

});';

ret += 'break;';

return ret;

}

function processSetTimeout(code,index){

//抽取时间

var time = code.match(/\d+/i)[0];

var ret = 'case '+index + ':';

ret += 'setTimeout(function(){\

process('+(index+1)+');\

},'+time+');';

ret += 'break;';

return ret;

}

6、执行拼接的字符串函数

eval(evalFunc);

最终实现的代码

上面是实现的思路,实现细节上没有写,下面是完整的实现代码,比较简陋的方式,提供一个实现的思路

//处理ajax代码逻辑

function processAjax(code,index){

//抽取请求地址

var url = code.match(/"([^"]+)"/i)[1];

//抽取出返回值赋值的变量名

var param = code.match(/(\w+)\s=\s/i)[1];

var ret = 'case '+index + ':';

ret += '$.ajax({\

type: "get",\

url: "'+url+'",\

dataType: "jsonp",\

success: function(__data){\

'+param+'=__data;\

process('+(index+1)+');\

}\

});';

ret += 'break;';

return ret;

}

//处理setTimeout逻辑

function processSetTimeout(code,index){

//抽取时间

var time = code.match(/\d+/i)[0];

var ret = 'case '+index + ':';

ret += 'setTimeout(function(){\

process('+(index+1)+');\

},'+time+');';

ret += 'break;';

return ret;

}

//具体包裹的函数实现,func为函数引用

function define(func){

var funcStr = func.toString();

//去掉functon和末尾的}

funcStr = funcStr.substring(12,funcStr.length-1);

//定义最终执行的函数字符串

var evalFunc = "!function(){";

//置顶所有变量定义

var varList = funcStr.match(/var\s([0-9a-zA-Z_\$]+)[,;\s]/gi);

evalFunc += varList.join(";") + ";";

//把var都去掉

funcStr = funcStr.replace(/var\s/gi,"");

//定义内部递归调用的函数,参数为模块编号

evalFunc += 'function process(__process_index){';

//分割后的模块编号

var switchIndex = 1;

//上一个分割模块的字符串位置

var lastProcessIndex = 0;

//利用switch来实现针对模块编号执行

evalFunc += 'switch(__process_index){';

//切分成多个模块,以$.ajax和setTimeout为分隔符

funcStr.replace(/(\w+\s=\s\$.ajax\([^\)]+\))|(setTimeout\(\d+\))/gi,function($a,$b){

//分割符前面的代码模块

evalFunc += 'case '+switchIndex+':';

evalFunc += funcStr.substring(lastProcessIndex, funcStr.indexOf($a));

//最终需要递归调用回下一个模块

evalFunc += 'process('+(switchIndex+1)+');break;';

//模块自增

switchIndex +=1;

//添加分割符的代码,$.ajax或者setTimeout

evalFunc += ($a.indexOf("$.ajax") > -1 ? processAjax($a,switchIndex) : processSetTimeout($a,switchIndex));

switchIndex +=1;

//把模块字符串位置往后移动

lastProcessIndex = funcStr.indexOf($a) + $a.length + 1;

});

//补齐分割完的最后的代码

evalFunc += 'case '+switchIndex+':';

evalFunc += funcStr.substring(lastProcessIndex);

evalFunc += 'break;';

evalFunc += '}';

evalFunc += "}process(1);}();";

//执行拼接好的函数

eval(evalFunc);

}

//调用方式

define(function(){

var start = new Date();

var time = $.ajax("http://tc.netease.com/zxz/datetime.php");

console.log(time);

setTimeout(2000);

console.log(new Date()-start);

});

实际上执行的代码

调用上面的define函数,实际上并非执行里面的函数引用,而是执行拼接好的函数字符串,最终上面生成的用来执行的函数会变成如下:

//自执行函数

!function(){

//将所有变量抽取置顶后的结果

var start ;var time ;

//定义了递归调用的函数

function process(__process_index){

//用switch来执行对应的模块,下面是根据$.ajax和setTimeout切割出来的执行模块

switch(__process_index){

case 1:

start = new Date();

process(2);

break;

case 2:

$.ajax({

type: "get",

url: "http://tc.netease.com/zxz/datetime.php",

dataType: "jsonp",

success: function(__data){time=__data;process(3);}

});break;

case 3:

console.log(time);

process(4);

break;

case 4:

setTimeout(function(){process(5);},2000);

break;

case 5:

console.log(new Date()-start);

break;

}

}

//默认执行模块1

process(1);

}();

后记

上面的方式,用于实际项目上还需要继续调整和细节处理,提供一个可行性。

可以依赖于构建工具或者Node等来实现将同步代码编译成异步代码执行,这样可以免去用define函数包裹

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值