JS的异步
1.异步
程序中现在运行的部分和将来运行的部分之间的关系是异步编程的核心。
多数JavaScript开发者从来没有认真思考过自己程序中的异步到底是如何出现的,以及为什么会出现,也没有探索过处理异步的其他方法。一直以来,低调的回调函数就算足够好的方法了。目前为止,还有很多人坚持认为回调函数完全够用。
但是,作为在浏览器、服务器以及其他能够想到的任何设备上运行的一流编程语言,JavaScript面临的需求日益扩大。为了满足这些需求,JavaScript的规模和复杂性也在持续增长,对异步的管理也越来越令人痛苦,这一切都迫切需要更强大、更合理的异步方法。
1.1 分块的程序
现在我们发出一个异步Ajax请求,然后在将来才能得到返回的结果(通过使用回调函数)。
//ajax(...)是某个库中提供的某个Ajax函数。
ajax("http://some.url.1",function myCallbackFunction(data){
console.log(data);//得到一些数据
});
function now(){
return 21;
}
function later(){
answer = answer * 2;
console.log("Meaning of life: ", answer);
}
var answer = now();
setTimeout(later, 1000);//Meaning of life: 42
setTimeout(...)设置了一个事件(定时)在将来执行,所以函数later()的内容会在之后的某个时间(从现在起1000毫秒之后)执行。
任何时候,只要把一段代码包装成一个函数,并指定它在响应某个事件(定时器、鼠标点击、Ajax响应等)时执行,你就是在代码中创建了一个将来执行的块,也由此在这个程序中引入了异步机制。
1.2 事件循环
现在我们来澄清一件事情(可能令人震惊):尽管你显然能够编写异步JavaScript代码,但直到最近(ES6),JavaScript才真正内建有直接的异步概念。
JavaScript引擎并不是独立运行的,它运行在宿主环境中,对多数开发者来说通常就是Web浏览器。经过最近几年的发展,JavaScript已经超过了浏览器的范围,进入了其他环境,比如通过像Node.js这样的工具进入服务器领域。实际上,JavaScript现如今已经嵌入到了从机器人到电灯泡等各种各样的设备中。
所有这些环境都提供了一种机制来处理程序中多个块的执行,且执行每个块时调用JavaScript引擎,这种机制被称为事件循环。
ES6中Promise对事件循环队列的调度运行能够直接进行精细控制。
1.3 并行
异步是关于现在和将来的时间间隙,而并行是关于能够同时发生的事情。
var a = 20;
function foo(){
a = a + 1;
}
function bar(){
a = a * 2;
}
ajax("...",foo);
ajax("...",bar);
由于JavaScript的单线程特性,foo()和bar()中的代码具有原子性。也就是说,一旦foo()开始运行,它的所有代码都会在bar()中的任意代码运行之前完成,或者相反。这称为完整运行特性。
1.4 并发
两个或多个“进程”同时执行就出现了并发。这里的“进程”之所以打上引号,是因为这并不是计算机科学意义上的真正操作系统级进程。这是虚拟进程,或者任务,表示一个逻辑上相关的运算序列。
1.5 任务
在ES6中,有一个新的概念建立在事件循环队列之上,叫作任务队列。这个概念给大家带来的最大影响可能是Promise的异步特性。
事件循环队列类似于一个游乐园游戏:玩过了一个游戏之后,你需要重新到队尾排队才能再玩一次。而任务队列类似于玩过了游戏之后,插队接着继续玩。
2.回调
回调是编写和处理JavaScript程序异步逻辑的最常用方式。
回调函数是JavaScript的异步主力军,并且它们不辱使命地完成了自己的任务。
2.1 continuation
//A
ajax("...",function(data){
//C
});
//B
//A和//B表示程序的前半部分,而//C标识了程序的后半部分。前半部分立刻执行,然后是一段时间不确定的停顿。在未来的某个时刻,如果Ajax调用完成,程序就会从停下的位置继续执行后半部分。
信任的问题
//C会延迟到将来发生,并且在第三方的控制下。我们把这称为控制反转,也就是把自己程序一部分的执行控制交给某个第三方。在你的代码和第三方工具之间有一份并没有明确表达的契约。
//过分信任输入
function addNumbers(x,y){
return x + y;
}
addNumbers(21,21);//42
addNumbers(21,"21");//"2121"
//针对不信任输入的防御性代码
function addNumbers(x,y){
if(typeof x != "number" || y != "number"){
throw Error("Bad parameters");
}
return x + y;
}
addNumbers(21,21);//42
addNumbers(21,"21");//Error: "Bad parameters"
//依旧安全但更好一些
function addNumbers(x,y){
x = Number(x);
y = Number(y);
return x + y;
}
addNumbers(21,21);//42
addNumbers(21,"21");//42
3.Promise
通过回调表达程序异步和管理并发的两个主要缺陷:缺乏顺序性和可信任性。
我们用回调函数来封装程序中的continuation,然后把回调交给第三方,期待其能够调用回调,实现正确的功能。通过这种形式,我们要表达的意思是:“这是将来要做的事情,要在当前的步骤完成之后发生”。
如果我们不把自己程序的continuation传给第三方,而是希望第三方给我们提供了解其任务何时结束的能力,然后我们自己的代码来决定下一步做什么。这种范式就称为Promise。
绝大多数JavaScript/DOM平台新增的异步API都是基于Promise构建的。
4.生成器
我们把注意力转移到一种顺序、看似同步的异步流程控制表达风格。使这种风格成为可能的“魔法”就是ES6生成器(generator)。
4.1 打破完整运行
var x = 1;
//下面是生成器函数
function *foo(){
x++;
yield;//暂停点
console.log("x: ",x);
}
function bar(){
x++;
}
var it = foo();//构造迭代器
it.next();//启动foo()
x;//2
bar();
x;//3
it.next();//x: 3
注意:function* foo(){...}、function *foo(){...}是一样的,唯一区别是*位置的风格不同。function*foo(){...}(没有空格)也一样,这只是风格偏好问题。
上述代码的运行过程:
it = foo()运算并没有执行生成器*foo(),而只是构造了一个迭代器(iterator),这个迭代器会控制它的执行。- 第一个
it.next()启动了生成器*foo(),并运行了*foo()第一行的x++。 *foo()在yield语句处暂停,在这一点上第一个it.next()调用结束。- 我们查看x的值,此时为2。
- 我们调用
bar(),它通过x++再次递增x。 - 我们再次查看x的值,此时为3.
- 最后的
it.next()调用从暂停处恢复了生成器*foo()的执行,并运行console.log(...)语句,这条语句使用当前x的值3。
相关阅读:知乎上关于生成器的解释
4.2 生成器+Promise
ES6中最完美的世界就是生成器(看似同步的异步代码)和Promise(可信任可组合)的组合。
获得Promise和生成器最大效用的最自然的方法就是yield出来一个Promise,然后通过这个Promise来控制生成器的迭代器。
推荐阅读:ECMAScript 6 入门
参考资料:《你不知道的JavaScript》(中卷) 第二部分 异步和性能
JS的异步的更多相关文章
- 【译】深入理解python3.4中Asyncio库与Node.js的异步IO机制
转载自http://xidui.github.io/2015/10/29/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3python3-4-Asyncio%E5%BA%93% ...
- js的异步加载你真的懂吗
面试高频之js的异步加载 讲这个问题之前, 我们从另一个面试高频问题来切入, 我们的web页面从开始解析到页面渲染完成都经历了什么 ? 1 , 创建document对象, 开始解析页面, ...
- js的异步和单线程
最近,同事之间做技术分享的时候提到了一个问题"js的异步是另开一个线程吗?"当时为此争论不休.会后自己查阅了一些资料,对这个问题进行一个自我的分析与总结,有不同意见的希望可以赐教, ...
- 探秘JS的异步单线程
对于通常的developer(特别是那些具备并行计算/多线程背景知识的developer)来讲,js的异步处理着实称得上诡异.而这个诡异从结果上讲,是由js的“单线程”这个特性所导致的. 我曾尝试用“ ...
- JS实现异步提交
什么是XMLHttpRequest? XMLHttpRequest对象用于在后台与服务器交换数据 XMLHttpRequst的作用 在不重新加载页面的情况下更新网页 在页面已加载后从服务器请求数据 在 ...
- JS的异步世界
前言 JS的异步由来已久,各种异步概念也早早堆在开发者面前.可现实代码中,仍然充斥了各种因异步顺序处理不当的bug,或因不好好思考,或因不了解真相.今天,就特来再次好好探索一番JS的异步世界. 01 ...
- JS的异步模式
JS的异步模式:1.回调函数:2.事件监听:3.观察者模式:4.promise对象 JavaScript语言将任务的执行模式可以分成两种:同步(Synchronous)和异步(Asychronous) ...
- Node.js之异步编程
> 文章原创于公众号:程序猿周先森.本平台不定时更新,喜欢我的文章,欢迎关注我的微信公众号. 方法详解
Python中格式化format()方法详解 Python中格式化输出字符串使用format()函数, 字符串即类, 可以使用方法; Python是完全面向对象的语言, 任何东西都是对象; 字符串的参 ...
- LimeSDR在windows下使用Gqrx来接收FM广播
本文内容.开发板及配件仅限用于学校或科研院所开展科研实验! 淘宝店铺名称:开源SDR实验室 LimeSDR链接:https://item.taobao.com/item.htm?spm=a230r.1 ...
- HTML学习1-Dom之事件绑定
事件: 1.注册事件 a. <div onxxxx=””></div> b. document .onxxxx= function() //找到这个标签 2.this,触发 ...
- map的运用
一.map是一种关联容器,支持高效的查找和访问 map中的元素是一些关键字-值(key-value)对: 关键字起索引作用: 值表示与索引相关联的数据. 关联容器中元素是根据关键字存储的,故其不支持位 ...
- Mininet-Wifi 多接入点(Access Point)实验
实验简介 这个实验来自Mininet-Wifi用户手册.在本实验中,我们会创建一个有三个AP的线式拓扑,并有三个站点(station)与每个AP通过无线相连.将通过这个时间简单演示一些Mininet ...
- “吃神么,买神么”的第二个Sprint计划(计划过程内容)
“吃神么,买神么”项目Sprint计划 ——6.1(第二天)立会内容与进度 团队组员各自任务: 陈键.吴舒婷:继续完善前台设局与布局 林欢雯.冯美欣:开展后台的界面的设计与布局 任务的进度: 陈键. ...
- angularJS1笔记-(14)-自定义指令(scope)
index.html: <!DOCTYPE html> <html lang="en"> <head> <meta charset=&qu ...
- iOS- 什么是GitHub?关于它的自我介绍「初识 GitHub」
1 前言 我一直认为 GitHub 是程序员必备技能,程序员应该没有不知道 GitHub 的才对,我当初接触 GitHub 也大概工作了一年多才开始学习使用,我读者里很多是初学者,而且还有很多是在校大 ...
- C++编译与链接(1)-编译与链接过程
大家知道计算机使用的一系列的1和0 那个一个C++语言程序又是如何从一个个.h和.cpp文件变成包含1和0的可执行文件呢? 可以认为有以下的几个环节 源程序->预处理->编译和优化-> ...
- Excel作为数据源TesTNG做数据驱动完整代码
说明:EXCEL 支持xls 和xlsx 俩种格式 : 已经过测试 ! package main.java; import org.apache.poi.ss.usermodel.*; import ...