话题有点大,对于关注前端圈圈的朋友,这个话题也有点落入俗套,做为森阿姨的第一篇掘金文,还希望能写出一点新鲜感和小启迪。森阿姨保证绝对原创。
我们都知道,js的世界是单线程执行的,也就是说一个任务完成之后才能进行另一个任务,这是因为js是运行在宿主进程多脚本语言,比如浏览器,比如node,宿主进程只会为其分配一个js引擎线程。那么对于耗时比较长的操作,如果继续等,也许会很受伤,用户体验大打折扣,待在那里啥也干不了。所以,我们需要一些方法来避免等待耗时多的操作,这就引出‘异步’了吗,别急,引擎在没有执行和将要执行的时候,怎么知道这个操作的耗时长短呢?
要真正的理解一个机制,最好的方法是,我们尝试自己设计它。现在,停下来,我们来自己想一想,如果我们开发浏览器引擎,我们该怎么做?
很多东西,我们每天都在写,浏览器引擎每天都在数以亿计的机器上运行,看起来那么自然,自然到我们忘了去想这背后的道理和逻辑,而认为这就是本该如此的,我们甘之如饴地享受着前人给的一切,忘了我们最初打开编辑器,敲下第一行代码时候的梦想。
你生气了,也许要问,如果是我,干脆多线程多好,为什么不采取多线程执行js呢?大部分语言都是多线程的,比如c++,比如java,你也许在去茶水间洗杯子的时候,听到后端的rd小哥哥在面试候选者的时候,总是要问怎么解决的线程间通信。可是,可是他们java啥的是面向不同的操作任务和数据的,而我们写的html,css以及js,根本来讲是面向网页的,说到底,是操作浏览器的dom元素的。而浏览器渲染dom元素,最怕的就是重绘与回流,而多线程来操作dom,必然导致更高的复杂度,以及更高概率的重绘与回流。
浏览器在解析的时候,已经把任务分了2类:同步任务、异步任务。js的单线程指的是同时只有一个js任务在运行,其他的同步任务会被放到后面排队等待,异步任务会被放到消息队列中。浏览器本身并不是单线程的,它还有EventLoop轮询线程、UI渲染线程、网络请求线程......等等其他很多线程在做不同的任务。同步任务直接进入js解析线程排队执行,异步任务会被执行线程挂起,进入消息队列,通过回调函数来告知主线程自己的任务结束了,不用在等了,这样主线程就会把这个任务也放到等待执行的队列。我们会在很多文章中看到‘执行栈’这个词,这个词森阿姨不想用,因为我们都知道栈的特点是先进后出,而这个js的执行,是先来先服务。而‘执行栈’这个词在这里,代表的不是js任务,而是为了这些任务的执行,分配的内存空间。计算机的好处就是诚恳,所有的一切都是这样井然有序,不会向人类,没有执行完,先喊一声执行结束了,什么插队啊加塞啊,统统不存在的,所有的任务都按照预先设置好的规则井井有条的运行。
我们的cpu速度远远高于网络I/O传输的速度,在数据驱动以及mvvm普及的今天,大量的异步操作充斥在我们代码中,一个又一个的回调,让我们的代码没有设计感又难于复用和维护。
在es6中,promise终于被提了出来,成为浏览器原生支持的方法,我们认为这本质也是一种语法糖,es6中很多东西都可以看作语法糖。没有这颗糖,我们的生活依旧可以继续,我们可以很快的写出满足业务需求的逻辑和代码,赚工资养家。可是,可是人生苦短,只有你甜,只有你甜。甜一点,有什么不好呢,又有什么好呢?我们不得不学习各种新技术、看各种文章、接触各种新名词,很多前端老人苦不堪言,很多前端新手菜鸟弯道超车。
先说回调函数的缺点:1)回调地狱,繁琐;2)代码结构混乱,同步任务和异步任务堆叠交杂;3)与设计感无关,无法使用先进的设计模式;4)所有的函数和逻辑通过回调实现,不方便其他模块复用;5)重复和冗余。
promise可以把异步过程串行起来写,例如:
new Promise(testFunction).then(function (result) {
}).catch(function (reason) {
}).then(function (result) {
}).catch(function (reason) {
}).then(function (result) {
}).catch(function (reason) {
});
复制代码
从这段代码中,我们可以看到异步操作就像同步操作一样,也顺序的被写在一个个的.then之后,即使我们不写返回值,promise的每一步也都有一个默认的返回值,叫做:undefined。
promise是符合A+规范的,我们也可以自己实现promise,并且使用A+规范来测试它。
promise有3中状态:pending(进行中)、resolved(解决)、rejected(失败),这个promise翻译成中文可以叫做承诺,因为只要达到来解决或者拒绝,就不会再改变。它诚恳又有分寸,它终究不是人,它只是那么单纯。