博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
给程序员看的Javascript攻略(完结)- 异步
阅读量:6122 次
发布时间:2019-06-21

本文共 3617 字,大约阅读时间需要 12 分钟。

原文发表在: , 本文是汉化重制版。

本系列在 上同步连载。

用ajax胡乱做项目的时候踩过好多坑,然后对JS留下了“非常诡异”的印象。系统学习后,发现这个构建了整个互联网表层的语言其实非常666。这次的学习已经告一段落,本篇也是这个系列的最后一部分。回头看来,把学习记录发出来这个经历挺奇特的,以前是写了给自己看,现在随便搞搞发来掘金就3000+的总阅读,顿时感觉有意义了很多。所以我也想明白了,你看,我就有动力写。

其实没啥新鲜的

简单来讲,异步有两层含义,1)让慢操作不要阻塞;2)非线性触发事件。稍稍讲深一点,在操作系统里,事件也叫中断,这里一次中断可以代表一个网络收包,一次时钟,或者一次鼠标点击,等。那从技术上层面看,一个事件可以中断当前进程,挂起下一条指令,并且“异步地”调用一个预设好的代码块(事件处理函数)。
应用层也一样。

阻塞操作的问题

狭义来说,异步可以解决应用阻塞(一般是I/O)的问题。为啥要聊异步一定要说阻塞呢?那我们从头来看看。每一个带UI的应用(无论是嵌入式的,还是APP,游戏还是一个网页),底下都一个循环在非常快的刷新屏幕,那如果这个循环被阻塞了,比如在这个循环上进行了一次网络请求,UI就卡了,用户也就跑了。而JavaScript就跑在这个循环上。
这次要先做点实验前准备。
首先,下载 。这个用来让Chrome给我们的跨站请求放行。
然后,我们用Python来实现一个慢服务(API):
from flask import Flaskimport timeapp = Flask(__name__)@app.route("/lazysvr")def recv():  time.sleep(10)  return "ok"if __name__ == "__main__":  app.run(host='***.***.***.***', threaded=True)复制代码
然后我们打开 ,(不然请求直接失败返回了),然后我们跑例子:
     复制代码
如果我们打开开发者面板,可以很容易观察到,代码会卡在下面这行:
xmlHttp.send( null ); // it is the aforementioned blocking operation复制代码
在卡住的这10秒左右,按钮是点不动的,然后浏览器才会跳出弹窗:
ok复制代码
并且,Chrome会抱怨:
[Deprecation] Synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects to the end user’s experience. For more help, check https://xhr.spec.whatwg.org/.复制代码
暂且把这个当作对这个问题的官方描述吧。

来一波异步

广义来讲,以下的都属于异步操作:
1)把慢操作放到其它线程执行;
2)由外部触发的事件;
3)两者的混合。
下面我会举三个例子来说明
第一个?,收包
这个例子的代码也可以解决上节的阻塞问题,
打上码:
     复制代码
在上面的代码里,我们1)把open()的第二个参数变成“true”,这样可以把慢操作负载到其它线程上去;2)注册一个回调函数来监听收报事件。这个回调函数在网络交互完成后会被立即执行。
这次按钮就可以点了,然后
ok复制代码
也按照预期弹出。
再来一个,时钟周期
直接先打上码:
setTimeout(callback, 3000);function callback() {  alert(‘event triggered’);}复制代码
注意1,JS从一开始就没有同步的sleep()函数;
注意2,和开始说的OS不一样,这个时钟是绝对不会触发进程调度的,正如之前提到,所有的JS代码都是运行在一条线程上。
第三个,点击鼠标
     复制代码
在上面的三个例子中,我们都给特定的事件(由非主循环触发)注册了回调函数。在第一个例子里,我们还把一个慢操作负载到了其它线程来解决卡死的问题。所有这些操作都可以用一个词来概括,异步!

新的fetch()接口

在第一个?中,我用回调来举例是因为比较直观。其实更好的办法是用fetch()来进行网络请求。这个函数会返回一个Promise对象,再用这个对象调用then()函数的话:

1. 异步操作的代码就可以变成线性(更像同步)了;

2. 回调地狱的问题可以得到解决了;

3. 所有的相关异常,可以在一个代码块里处理了:

     复制代码

运行结果和第一个?一样,我还是留了按钮给你试UI有没有卡。

底层机制,多线程+事件循环

JS不是单线程吗?

答案是,即是也不是。什么意思?

var i;for (i = 0; i < 1000; i++) {  var xmlHttp = new XMLHttpRequest();  xmlHttp.open( "GET", "http://***.***.***.***:5000/lazysvr", true );  xmlHttp.onreadystatechange = function() {     if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {        alert(xmlHttp.responseText);    }  } // end of the callback  xmlHttp.send( null );}复制代码

假设浏览器的pid是666(巧了,我做这个测试的时候还真是),我们用一小段脚本(环境是Mac)本来观察线程状态:

#!/bin/bashwhile true; do ps -M 666; sleep 1; done复制代码

初始值(我把无关的列和行都干掉了):

USER     PID ... STIME    UTIMEholmes   666 ... 0:00.42  0:01.47 ......         666     0:00.20  0:00.64复制代码

结束的时候:

USER     PID ... STIME    UTIMEholmes   666 ... 0:00.50  0:01.88 ......         666     0:00.37  0:01.28复制代码

除了主线程,还有一条非常活跃的线程,我估摸着这条是用来监听网络的(多路复用)套接字。

所以JS代码确实是运行在一条线程里。但是如果从应用程序的角度来看,它其实是多线程。用同样的办法测一下Node吧。

“粗”暴的事件循环

上文提到,操作系统的中断是以指令为粒度的,但是这个传说中的事件循环,粒度就有点大了:

var i;for (i = 0; i < 3; i++) {  alert(i);}setTimeout(callback, 0);function callback() {  alert(‘event triggered’);}复制代码

我们都知道结果是:

123event triggered复制代码

简单来说呢,虽然我们注册了一个定时事件,并且指定它立即执行,但是JS引擎还是在运行时忠实的把本次循环跑完,才会去理刚刚注册的那个事件。

这个代表一般事件中断是以指令周期为单位,而JS是以循环周期为单位的。

有点尴尬了,这么大粒度的事件处理会不会导致UI响应时间长呢?我觉得其实不会。即使在以指令周期为单位的事件响应里,用户的操作还是需要在本次"循环周期"结束放到主线程来,然后反映到UI。因为一切UI更新都要在主线程。所以,这个极其简化的单线程设计本身并不会对UI性能造成影响。你觉得呢?

迟到的总结

这个系列中,我覆盖了在JS里,。然后我在和里深入到prototype这一层进一步讨论了一下对象。最重要的是,我三次提及了this的坑:

说明真的很重要。

最后就是本篇了,用我理解的角度聊了一下异步。

如果你还记得的话,这个系列是我为新工作(临时)学JS准备的。以现在上手程度来看,我觉得这个底子打的还不错,希望对你也一样。但是这个文章并不全面,所以我准备了如下的附加阅读:

我用来调试的方法

这篇很有趣,我第一次读到,希望有机会能翻译

, 最重要的事,

常来掘金看篇。

最后要承认第一段的结构是模仿乔帮主在第一次苹果(iPhone1)发布会的经典段式。(写这篇文章的时候,实在被最新的发布会感动了一把)。如果没看过去找找吧。

感谢阅读,后会有期!

转载地址:http://yjwua.baihongyu.com/

你可能感兴趣的文章
linux后台运行程序
查看>>
win7 vs2012/2013 编译boost 1.55
查看>>
IIS7如何显示详细错误信息
查看>>
C++文件读写详解(ofstream,ifstream,fstream)
查看>>
Android打包常见错误之Export aborted because fatal lint errors were found
查看>>
Tar打包、压缩与解压缩到指定目录的方法
查看>>
新手如何学习 jQuery?
查看>>
配置spring上下文
查看>>
Python异步IO --- 轻松管理10k+并发连接
查看>>
mysql-python模块编译问题解决
查看>>
Oracle中drop user和drop user cascade的区别
查看>>
【Linux】linux经常使用基本命令
查看>>
HTML模块化:使用HTML5 Boilerplate模板
查看>>
登记申请汇总
查看>>
Google最新截屏案例详解
查看>>
2015第31周日
查看>>
在使用EF开发时候,遇到 using 语句中使用的类型必须可隐式转换为“System.IDisposable“ 这个问题。...
查看>>
Oracle 如何提交手册Cluster Table事务
查看>>
BeagleBone Black第八课板:建立Eclipse编程环境
查看>>
在服务器上用Fiddler抓取HTTPS流量
查看>>