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

标签: javascript | 发表时间:2015-06-29 02:54 | 作者:ARGUS
出处:http://segmentfault.com/blogs

1. 异步与同步 技术研究

(1). 概念介绍

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

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


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

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

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

  JavaScript  var 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: "<!DOCTYPE HTML PUBLIC ...", status: 200, statusText: "OK"}

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

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

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


(3). 异步的困惑

先帖一段代码:
a.php

  php  <?php
sleep(1);      // 休息一秒钟
echo '{}';

page.js

  JavaScript  for( 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  <?php
sleep(1);
echo '{i: ' . $_POST['data'] . '}'; // 这一行改了

page.js

  JavaScript  for( 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

  JavaScript  for( 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/.

clipboard.png

(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

相关 [javascript 异步 应用] 推荐:

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

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

Javascript异步编程的4种方法

- - 阮一峰的网络日志
你可能知道,Javascript语言的执行环境是"单线程"(single thread). 所谓"单线程",就是指一次只能完成一件任务. 如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务,以此类推. 这种模式的好处是实现起来比较简单,执行环境相对单纯;坏处是只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行.

Atmosphere 1.0:支持Java/JavaScript的异步通信框架

- - InfoQ cn
Atmosphere 1.0是一个新的Java/Scala/Groovy框架,它试图将Web浏览器与应用服务器之间的通信抽象出来. 在Web Socket、HTML5服务器端事件和其他特定于应用服务器的解决方案可用时,该框架可以透明地支持,此外还可将长轮询作为一种备选方案. 最初,Web应用程序是采用客户端/服务器模型构建的,始终由客户端向服务器发起连接.

使用async属性异步加载执行JavaScript

- - WebHek
HTML5让我兴奋的一个最大的原因是,它里面实现的新功能和新特征都是我们长久以来一直期待的. 比如,我以前一直在使用 placeholders,但以前必须要用JavaScript实现. 而HTML5里给JavaScript标记提供的 async属性,使JavaScript能异步加载执行. 之前我需要各种的JavaScript插件来实现这种功能,但现在这个新属性能让我们轻松的实现异步加载.

提高 web 应用性能之 JavaScript 性能调优

- 去北方-Jack - IBM developerWorks 中国 : 文档库
JavaScript 是一个比较完善的前端开发语言,在现今的 web 开发中应用非常广泛,尤其是对 Web 2.0 的应用. 随着 Web 2.0 越来越流行的今天,我们会发现:在我们的 web 应用项目中,会有大量的 JavaScript 代码,并且以后会越来越多. JavaScript 作为一个解释执行的语言,以及它的单线程机制,决定了性能问题是 JavaScript 的软肋,也是 web 软件工程师们在写 JavaScript 需要高度重视的一个问题,尤其是针对 Web 2.0 的应用.

Javascript框架和jQuery应用情况的信息图表(Infographic)

- Will - ITeye资讯频道
我们总是想知道时下最流行的一些JavaScript框架和由于这些框架所开发的Web应用程序的比较情况. 下面就是一张体现这些比较数据的信息图表. 它包括了jQuery、Mootools、Prototype、YUI、Dojo、Extjs等这些框架的被使用情况. 从信息图表中你可以看出,比较有意思的一点就是1.3.2版本的jQuery是目前最流行的版本.

使用 SpiderMonkey 使 C++应用支持 JavaScript 脚本引擎

- DreamToTrue - C++博客-牵着老婆满街逛
转载自:http://dozb.bokee.com/1919675.html.   使用 SpiderMonkey 使 C++应用支持 JavaScript 脚本引擎. 翻译:dozb         英文版. 这个教程的目的是演示如何使你的 C++ 应用能够解释执行 JavaScript 脚本. SpiderMonkey, 是 Mozilla 项目的一部分, 是一个执行JavaScript脚本的引擎.

Codecademy – Javascript 语言教学 | 小众软件 > 在线应用

- 2楼水饺 - 小众软件
戒烟很容易,壤驷二狗戒过好多次了. 编程也很容易,二狗学过好多次了. Codecademy 是 Javascript 编程互动教学网站,通过互动对话的方式相对友好地帮新手入门. 访问: 官网 | 来自小众软件. ©2011 Thruth for 小众软件 | 原文链接 | 0 留言 | 加入我们 | 投稿 | 订阅指南.

【转载】Javascript SDK:轻松开发HTML5应用的必备工具

- - HTML5研究小组
Javascript SDK:轻松开发HTML5应用的必备工具. annie 2012-05-31 16:03    1条评论. 运算平台  Parse最新发布Javascript SDK,有了它,开发HTML 5应用变得更加简单轻松. Parse 是由Y Coumbinator所孵化的 创业公司,开发者能够在上面创建自己的应用,Parse 更加专注于移动开发者.

Parse将推出Javascript SDK支持移动网页应用

- - CocoaChina移动观察
文/Kim-Mai Culter. 由美国知名创业孵化器Y Combinator支持的创业Parse曾为面向移动应用(如Band of the Day 和Hipmunk)提供后端支持,宣布将为移动网络应用提供支持. 这家位于旧金山的创业公司提供了面向JavaScript的SDK,实现更为方便的创建HTML5应用.