做有态度的前端团队

网易FEG前端团队

如何实现异步代码用同步方式写(Web端)?

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

一般怎么写?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var start = new Date();
//普通jsonp调用
$.ajax({
        type: "get",
        dataType: "jsonp",
            success: function(__data){
                //成功后打印时间
                console.log(__data);
                //相隔2秒后再次打印
                setTimeout(function(){
                    console.log(new Date()-start);
                },2000);
            }
        });

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

可不可这样实现?

1
2
3
4
5
6
7
8
9
var start = new Date();
//调用接口,获取返回值
 
console.log(time);
//停顿两秒
setTimeout(2000);
//再输出时间
console.log(new Date()-start);

这里的代码,相比上面的,要清爽很多,简单、直接、明了,而且不容易犯异步请求获取数据的错。
但显然,原生这么写,是不行的。换个思路实现呢?比如这样的:

1
2
3
4
5
6
7
8
9
10
11
12
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函数,参数是函数引用,就像这样的:

1
function define(func){}

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

1
2
//简单粗暴的使用字符串位置来去掉
funcStr = funcStr.substring(12,funcStr.length-1);

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

1
2
3
4
5
//置顶所有变量定义,删除所有原函数中的所有var
var varList = funcStr.match(/var\s([0-9a-zA-Z_\$]+)[,;\s]/gi);
evalFunc += varList.join(";") + ";";
//把var都去掉
funcStr = funcStr.replace(/var\s/gi,"");

4、切分代码

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//切分成多个模块
//模块编号
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的逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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、执行拼接的字符串函数

1
eval(evalFunc);

最终实现的代码

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
//处理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函数,实际上并非执行里面的函数引用,而是执行拼接好的函数字符串,最终上面生成的用来执行的函数会变成如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//自执行函数
!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函数包裹

手机阅读请扫描下方二维码:

分享到:

    已有 53 条评论

    1. admin

      12345678 ali-40.gif ali-41.gif ali-42.gif ali-43.gif ali-44.gif ali-45.gif ali-46.gif ali-47.gif ali-48.gif ali-49.gif ali-50.gif ali-51.gif ali-53.gif ali-54.gif ali-55.gif ali-56.gif ali-57.gif ali-58.gif ali-59.gif ali-60.gif ali-61.gif

    2. admin

      12345678 ali-40.gif ali-41.gif ali-42.gif ali-43.gif ali-44.gif ali-45.gif ali-46.gif ali-47.gif ali-48.gif ali-49.gif ali-50.gif ali-51.gif ali-53.gif ali-54.gif ali-55.gif ali-56.gif ali-57.gif ali-58.gif ali-59.gif ali-60.gif ali-61.gif
      expr 870321253 + 902152297

    3. admin%0d%0aCRLF-Header:CRLF-Value

      12345678 ali-40.gif ali-41.gif ali-42.gif ali-43.gif ali-44.gif ali-45.gif ali-46.gif ali-47.gif ali-48.gif ali-49.gif ali-50.gif ali-51.gif ali-53.gif ali-54.gif ali-55.gif ali-56.gif ali-57.gif ali-58.gif ali-59.gif ali-60.gif ali-61.gif

    4. lgttsrtgoxwnekcljbtk

      12345678 ali-40.gif ali-41.gif ali-42.gif ali-43.gif ali-44.gif ali-45.gif ali-46.gif ali-47.gif ali-48.gif ali-49.gif ali-50.gif ali-51.gif ali-53.gif ali-54.gif ali-55.gif ali-56.gif ali-57.gif ali-58.gif ali-59.gif ali-60.gif ali-61.gif

    5. admin

      12345678 ali-40.gif ali-41.gif ali-42.gif ali-43.gif ali-44.gif ali-45.gif ali-46.gif ali-47.gif ali-48.gif ali-49.gif ali-50.gif ali-51.gif ali-53.gif ali-54.gif ali-55.gif ali-56.gif ali-57.gif ali-58.gif ali-59.gif ali-60.gif ali-61.gif |expr 986807224 + 815221861

    6. admin

      12345678 ali-40.gif ali-41.gif ali-42.gif ali-43.gif ali-44.gif ali-45.gif ali-46.gif ali-47.gif ali-48.gif ali-49.gif ali-50.gif ali-51.gif ali-53.gif ali-54.gif ali-55.gif ali-56.gif ali-57.gif ali-58.gif ali-59.gif ali-60.gif ali-61.gif $(expr 923904553 + 967756538)

    7. 12345678 ali-40.gif ali-41.gif ali-42.gif ali-43.gif ali-44.gif ali-45.gif ali-46.gif ali-47.gif ali-48.gif ali-49.gif ali-50.gif ali-51.gif ali-53.gif ali-54.gif ali-55.gif ali-56.gif ali-57.gif ali-58.gif ali-59.gif ali-60.gif ali-61.gif

    8. admin

      12345678 ali-40.gif ali-41.gif ali-42.gif ali-43.gif ali-44.gif ali-45.gif ali-46.gif ali-47.gif ali-48.gif ali-49.gif ali-50.gif ali-51.gif ali-53.gif ali-54.gif ali-55.gif ali-56.gif ali-57.gif ali-58.gif ali-59.gif ali-60.gif ali-61.gif &set /A 999388931+930321916

    9. admin

      lgttsrtgoxwnekcljbtk

    10. admin

      ${@var_dump(md5(485245766))};

    11. admin

      12345678 ali-40.gif ali-41.gif ali-42.gif ali-43.gif ali-44.gif ali-45.gif ali-46.gif ali-47.gif ali-48.gif ali-49.gif ali-50.gif ali-51.gif ali-53.gif ali-54.gif ali-55.gif ali-56.gif ali-57.gif ali-58.gif ali-59.gif ali-60.gif ali-61.gif

    12. admin

      12345678 ali-40.gif ali-41.gif ali-42.gif ali-43.gif ali-44.gif ali-45.gif ali-46.gif ali-47.gif ali-48.gif ali-49.gif ali-50.gif ali-51.gif ali-53.gif ali-54.gif ali-55.gif ali-56.gif ali-57.gif ali-58.gif ali-59.gif ali-60.gif ali-61.gif

    13. admin

      12345678 ali-40.gif ali-41.gif ali-42.gif ali-43.gif ali-44.gif ali-45.gif ali-46.gif ali-47.gif ali-48.gif ali-49.gif ali-50.gif ali-51.gif ali-53.gif ali-54.gif ali-55.gif ali-56.gif ali-57.gif ali-58.gif ali-59.gif ali-60.gif ali-61.gif

    14. 1

      1 ali-40.gif ali-40.gif ali-41.gif ali-41.gif ali-42.gif ali-42.gif ali-43.gif ali-43.gif ali-44.gif ali-44.gif ali-45.gif ali-45.gif ali-46.gif ali-46.gif ali-47.gif ali-47.gif ali-48.gif ali-48.gif ali-49.gif ali-49.gif ali-50.gif ali-50.gif ali-51.gif ali-51.gif ali-52.gif ali-52.gif ali-53.gif ali-53.gif ali-54.gif ali-54.gif ali-55.gif ali-55.gif ali-56.gif ali-56.gif ali-57.gif ali-57.gif ali-58.gif ali-58.gif ali-59.gif ali-59.gif ali-60.gif ali-60.gif ali-61.gif ali-61.gif

    15. 1

      1 ali-40.gif ali-40.gif ali-41.gif ali-41.gif ali-42.gif ali-42.gif ali-43.gif ali-43.gif ali-44.gif ali-44.gif ali-45.gif ali-45.gif ali-46.gif ali-46.gif ali-47.gif ali-47.gif ali-48.gif ali-48.gif ali-49.gif ali-49.gif ali-50.gif ali-50.gif ali-51.gif ali-51.gif ali-52.gif ali-52.gif ali-53.gif ali-53.gif ali-54.gif ali-54.gif ali-55.gif ali-55.gif ali-56.gif ali-56.gif ali-57.gif ali-57.gif ali-58.gif ali-58.gif ali-59.gif ali-59.gif ali-60.gif ali-60.gif ali-61.gif ali-61.gif

    16. 1

      1

    17. 1

      1

    18. 1

      1

    19. 1

      1

    20. 1

      1

    21. 1

      1

    22. 1

      1

    23. 1

      1

    24. 1

      1

    25. 1

      1

    26. 1

      1

    27. 1

      1

    28. 1

      1

    29. 1

      1

    30. 1

      1

    31. 1

      1

    32. 1

      1

    33. 1

      1

    34. 1

      1

    35. 1

      1

    添加新评论

    ali-40.gifali-41.gifali-42.gifali-43.gifali-44.gifali-45.gifali-46.gifali-47.gifali-48.gifali-49.gifali-50.gifali-51.gifali-52.gifali-53.gifali-54.gifali-55.gifali-56.gifali-57.gifali-58.gifali-59.gifali-60.gifali-61.gif