资讯专栏INFORMATION COLUMN

JavaScript 异步机制及应用 入门教程

BoYang / 3666人阅读

摘要:异步与同步技术研究概念介绍异步简写同步简写用比方来比喻异步就是个人同时起跑起点和出发时间相同在起跑时不去关心其他人会啥时候跑完尼玛这不废话吗大家都才起跑怎么知道别人多就跑完同步就是个人接力跑起点和出发时间不同且后一个人会等待前一个人跑完才能

1. 异步与同步 技术研究 (1). 概念介绍

异步: asynchronous 简写async
同步: synchronous 简写sync

用比方来比喻
异步就是: N个人同时起跑, 起点和出发时间相同, 在起跑时不去关心其他人会啥时候跑完~尼玛这不废话吗?大家都才起跑怎么知道别人多就跑完.
同步就是: N个人接力跑, 起点和出发时间不同, 且后一个人会等待前一个人跑完才能继续跑, 也就是要关心前一个人的结果(上一行代码的返回值).

(2). JS里面的异步/同步

JS运行场景多数是在用户浏览器上, 程序效率优劣会直接影响用户的体验交互. 比如一个网站, 在用户注册时, 会ajax校验输入再发提交表单, 如果用同步就可能会一直卡着等待ajax响应, 好几秒结束后再跳到注册结果页, 这个体验将是非常糟糕的.

说到JS的异步, 不得不提及一个非常有代表意义函数了.

JavaScriptvar url = "/action/";
var data = "i=1";
xmlHTTP = new XMLHttpRequest();
xmlHTTP.nonce = nonce;
xmlHTTP.open("POST", url);
xmlHTTP.onreadystatechange = function(a) {
    if(a.target.readyState!=4)return false;
    try{
        console.log(a.target.responseText)
    }catch(e){
        return false;
    }
};
xmlHTTP.send(data);

或者在jQuery写作:

JavaScript$.ajax({
    url: "/action/",
    type: "POST",
    data: "i=1",
    success: function(responseText){
        console.log(responseText);
    }
})

上面的无论是xmlHTTP.onreadystatechange, 还是success, 在JavaScript中均称为回调方法,
以原生JS的XMLHttpRequest为例, xmlHTTP变量是个XMLHttpRequest对象, 他的onreadystatechange是在每次请求响应状态发生变化时会触发的一个函数/方法, 然后在发出请求xmlHTTP.send(data)的时候, JS并不会理会onreadystatechange方法, 而当改送请求到达服务器, 开始响应或者响应状态改变时会调用onreadystatechange方法:

也就是
1) 请求发出
2) 服务器开始响应数据
3) 执行回调方法, 可能执行多次

以jQuery版为例, $.ajax本身是个函数, 唯一一个参数是{...} 这个对象, 然后回调方法success是作为这个对象的一个属性传入$.ajax的.
$.ajax()先将数据post到"/action/", 返回结果后再调用success(如果发生错误会调用error).
也就是

 1) 请求发出
 2) 服务器开始响应数据
 3) 响应结束执行回调方法

然后作为函数$.ajax, 是函数就应该有返回值(哪怕没有return也会返回undefined), 他本身的返回值是多少呢?
分为async:trueasync:false两个版本:

async:true版本:

JavaScript$.ajax({"url":"a.html", type:"GET", async:true})
> Object {readyState: 1}

async:false版本:

JavaScript$.ajax({"url":"robots.txt", type:"GET", false})
> Object {readyState: 4, responseText: "

我们可以直接看到, async:true异步模式下, jquery/javascript未将结果返回... 而async:false就将结果返回了.

然后问题就来了, 为什么async:true未返回结果呢?
答案很简单:
因为在返回的时候, 程序不可能知道结果. 异步就是指不用等此操作执行出结果再往下执行, 也就是返回的值中未包含结果.

留下一个问题, 我们是不是为了程序流程的简单化而使用同步呢?

(3). 异步的困惑

先帖一段代码:
a.php

php

page.js

JavaScriptfor( i = 1; i <= 4; i++ ){
    $.ajax({
        url: "a.php",
        type: "POST",
        dataType: "json",
        data: {data: i},
        async: true,         // 默认即为异步
        success: function(json) {
            console.log(i + ": " + json); // 打印
        }
    });
}

你们猜猜打印的那行会最终打印出什么内容?

1: {}
2: {}
3: {}
4: {}

吗?

错!

输出的将是:

4: {}
4: {}
4: {}
4: {}

你TM在逗我?
没有, 这并不是JS的BUG, 也不是jQuery的BUG.
这是因为, PHP休息了一秒, 而js异步地循环从1到4, 远远用不到1秒.
然后在1秒钟后, 才开始返回数据, 触发success, 此时此刻i已经自增成了4.
自然而然地, 第一次console.log(i...)就是4, 第二次也是, 第三次也是, 第四次也是.
那么如果我们希望程序输出也1,2,3,4这样输出怎么办呢?

两种方案:

1) 让后端输出i

a.php

php

page.js

JavaScriptfor( i = 1; i <= 4; i++ ){
    $.ajax({
        url: "a.php",
        type: "POST",
        dataType: "json",
        data: {data: i},
        async: true,
        success: function(json) {
            console.log(json.i + ": " + json); // 这一行改了
        }
    });
}

2) 给回调的事件对象赋属性

a.php

php保持原代码不变

page.js

JavaScriptfor( i = 1; i <= 4; i++ ){
    ajaxObj = $.ajax({          // 将ajax赋给ajaxObj
        url: "a.php",
        type: "POST",
        dataType: "json",
        data: {data: i},
        async: true,
        success: function(json, status, obj) {    // 增加回调参数, jQuery文档有说第三个参数就是ajax方法产生的对象.
                console.log(obj.i + ": " + json); // 从jQuery.ajax返回的对象中取i
        }
    });
    ajaxObj.i = i;            // 给ajaxObj赋属性i 值为循环的i 
}
有可能你会感到困惑, 为何可以给ajaxObj设置一个i属性然后在回调时用第三个回调参数的i属性呢?

jQuery.ajax文档中写到:

jQuery.ajax( [settings ] )
settings
...
success: Function( Anything data, String textStatus, jqXHR jqXHR )
第1个参数就是响应的文本/HTML/XML/数据/json之类的, 跟你的dataType设置有关
第2个参数就是status状态, 如success
第3个参数就是jqXHR, 也就是jQuery的XMLHttpRequest对象, 当然, 在这里就是$.ajax()生成的对象, 也就是事件的触发者本身, 
给本身设置一个属性(ajaxObj.i = i), 然后再调用本身的回调时, 使用本身的那个属性(obj.i), 当然会保持一致了.

然后
1)输出的结果将是

1: {i:1}
2: {i:2}
3: {i:3}
4: {i:4}

2)输出的结果将是

1: {}
2: {}
3: {}
4: {}

虽然略有区别, 但两者均可达到要求. 若要论代码的逼格, 相信你一定会被第二个方案给震惊的.
凭什么你给ajaxObj赋个属性就可以在success中用了呢?

请看(4). 异步的回调机制

(4). 异步的回调机制 ------ 事件

一个有经验的JavaScript程序员一定会将js回调用得得心应手.
因为JavaScript天生异步, 异步的好处是顾及了用户的体验, 但坏处就是导致流程化循环或者递归的逻辑明明在别的语言中无任何问题, 却在js中无法取得期待的值...
而JavaScript异步在设计之初就将这一点考虑到了. 任何流行起来的JS插件方法, 如jQuery的插件, 一定考虑到了这一点了的.

举个例子.

ajaxfileupload插件, 实现原理是将选择的文件$.clone到一个form中, form的target设置成了一个页面中的iframe, 然后定时取iframe的contents().body, 即可获得响应的值.
如果要支持multiple文件上传(一些现代化的浏览器支持), 还是得要用`XMLHttpRequest`

如下面代码:

$("input#file").on("change", function(e){
    for(i = 0; i < e.target.files.length; i++ ){
        var data = new FormData();
        data.append("file", e.target.files[i]);
        xmlHTTP = new XMLHttpRequest();
        xmlHTTP.open("POST", s.url);
        xmlHTTP.onreadystatechange = function(a) { // a 为 事件event对象
            if(a.target.readyState!=4)return false; // a.target为触发这个事件的对象 即xmlHTTP (XMLHttpRequest) 对象
            try{
                console.log(a.target.responseText);
            }catch(e){
                return false;
            }
        };
        xmlHTTP.send(data);
    }
})

你可以很明显地知道, 在onreadystatechange调用且走到console.log(a.target.responseText)时, 如果服务器不返回文件名, 我们根本并不知道返回的是哪个文件的URL. 如果根据i去取的话, 那么很容易地, 我们只会取到始终1个或几个, 并不能保证准确.
那么我们应该怎么去保证在console.log(a.target.responseText)时能知道我信上传的文件的基本信息呢?

$("input#file").on("change", function(e){
    for(i = 0; i < e.target.files.length; i++ ){
        var data = new FormData();
        data.append("file", e.target.files[i]);
        xmlHTTP = new XMLHttpRequest();
        xmlHTTP.file = e.target.files[i];
        xmlHTTP.open("POST", s.url);
        xmlHTTP.onreadystatechange = function(a) {
            if(a.target.readyState!=4)return false;
            try{
                console.log(a.target.file);         //这儿是上面`xmlHTTP.file = e.target.files[i]` 赋进去的
                console.log(a.target.responseText);
            }catch(e){
                return false;
            }
        };
        xmlHTTP.send(data);
    }
})

是不是很简单?

2. 展望 (1). Google对同步JavaScript的态度

在你尝试在chrome打开的页面中执行async: false的代码时, chrome将会警告你:

Synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects to the end user"s experience. For more help, check http://xhr.spec.whatwg.org/.

(2). 职场展望

异步和事件将是JavaScript工程师必备技能

[完]
Reference:

1.《Javascript异步编程的4种方法》     http://www.ruanyifeng.com/blog/2012/12/asynchronous%EF%BC%BFjavascript.html
2.《什么是 Event Loop?》            http://www.ruanyifeng.com/blog/2013/10/event_loop.html
3.《JavaScript 运行机制详解:再谈Event Loop》 http://www.ruanyifeng.com/blog/2014/10/event-loop.html

补充:
异步的数据一致性问题也可以用v1兄提供的方法来解决:

for(i=1; i < 4; i++){
    (function(i){
        $.ajax({
            url: "URL",
            type: "get",
            dataType: "text",
            success: function(response){
                console.log(response, i);
            }
        });
    })(i);
}

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/85771.html

相关文章

  • 2017-06-27 前端日报

    摘要:前端日报精选漫谈函数式编程一十年踪迹的博客前端每周清单的优势与劣势有望超越在嵌入式及物联网的应用现状进阶系列高阶组件详解一前端之路译如何充分利用控制台掘金程序猿升级攻略众成翻译中文译如何充分利用控制台掘金前端从强制开启压缩探 2017-06-27 前端日报 精选 漫谈 JS 函数式编程(一) - 十年踪迹的博客前端每周清单: Vue的优势与劣势;Node.js有望超越Java;JS在嵌...

    Eidesen 评论0 收藏0
  • JS高级入门教程

    摘要:解析首先简称是由欧洲计算机制造商协会制定的标准化脚本程序设计语言。级在年月份成为的提议,由核心与两个模块组成。通过引入统一方式载入和保存文档和文档验证方法对进行进一步扩展。其中表示的标记位正好是低三位都是。但提案被拒绝了。 JS高级入门教程 目录 本文章定位及介绍 JavaScript与ECMAScript的关系 DOM的本质及DOM级介绍 JS代码特性 基本类型与引用类型 JS的垃...

    zsy888 评论0 收藏0
  • 大型网站技术架构-入门梳理

    摘要:使用缓存两个前提条件数据访问热点不均衡数据某时段内有效,不会很快过期反向代理本地缓存分布式缓存异步旨在系统解耦。 大型网站技术架构-入门梳理 标签 : 架构设计 [TOC] 罗列了大型网站架构涉及到的概念,附上了简单说明 前言 本文是对《大型网站架构设计》(李智慧 著)一书的梳理,类似文字版的思维导图 全文主要围绕性能,可用性,伸缩性,扩展性,安全这五个要素 性能,可用性,伸缩性...

    wawor4827 评论0 收藏0
  • H5学习

    摘要:为此决定自研一个富文本编辑器。本文,主要介绍如何实现富文本编辑器,和解决一些不同浏览器和设备之间的。 对ES6Generator函数的理解 Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。 JavaScript 设计模式 ② 巧用工厂模式和创建者模式 我为什么把他们两个放在一起讲?我觉得这两个设计模式有相似之处,有时候会一个设计模式不能满...

    aristark 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<