嘿,大家好呀,今天这篇文章和大家聊聊 ES6 的异步编程实现。当然,ES6 中提出了多种解决方案,我们今天先聊聊 Promise 这个异步编程解决方案。
异步编程
JavaScript 中为什么会有异步编程这个概念呢?老生常谈的答案,因为 JavaScript 是单线程的。那么是什么造就了 JavaScript 的单线程呢?在浏览器中,单线程的 JavaScript 的运行机制又是如何的呢?好的,我们先来扯扯这两个问题。
JavaScript 单线程
单线程是历史的原因。很简单,这和 JavaScript 的诞生使命有关。JavaScript 作为浏览器的脚本语言,最初的使命(当然现在大部分也还是 )是用来操作 DOM,产出各种酷炫的用户交互体验。对于浏览器来说,DOM 是浏览器这个环境的一份公共资源。如果 JavaScript 是多线程的,简单点来说,就是两个线程的话,在这种环境中都要考虑很多同步编程的复杂性问题。例如:一个线程要删除一个 DOM 节点,但是另一个线程同时要修改这个 DOM 节点,DOM 是一个共有资源,如何做资源的 lock,然和去调度线程任务,等等这些复杂的问题,对于浏览器这个场景来说,太庞大了。当然,这只是一个部分原因。所以,设计之初,JavaScript 就是以单线程的身份降临世间的。那么,遇到耗时的阻塞任务怎么办,单线程被堵死,那用户岂不是只能盯着浏览器页面,啥子都做不了啥?这时候就需要异步啦,让异步来平息阻塞任务这些暴民。浏览器环境是如何去做的呢?我们接着看。。。
JavaScript 浏览器运行机制
Event Loop
好,让我们来瞻仰一下大神 Philip Roberts 的分析图:
这张图分析的很清晰,stack 是 execution contexts,执行上下文,我们可以理解为 JavaScript 执行的主线程。可能用一个等式来说明更加简洁明了:
在主线程执行的过程中,会执行各种各样的 function
,产生各种异步操作,如 DOM 操作、ajax
请求,setTimeout()
等等。这些操作,会在 task queue(callback queue)中产生对应的 event,当 stack 空闲时,event loop 的机制就会去 task queue 中取排在最前面的 event 进入 stack 执行,以此往复。。。
就是这个原因,所以,下面代码两种写法都是可以的:
异步编程解决方案
这也有一个发展的过程,最早的回调函数,到 Promise 对象,再到 Generator 和现在 ES6 的 async 函数,每一次解决方案都在解决不同的编程痛点吧。这里面,Promise 就是我们今天要谈论的对象。Promise 这个异步解决方案很有特点,它非常的“基础”、灵活,灵活到现在很多工具库的异步操作都是它来分装的(经常看库的同学有没有深有体会啊,如果理解的不是太好,很容易迷失在各种 Promise
、 .then
、 return
中)。
通俗的来说,一个 Promise 对象,代表着一个目前不可用,但是将来某个时间点可以被解析的值,当然,这个上面绑定这好几个处理函数喽(如:.then
.all
)。
Promise 机制
Promise 对象拥有三种状态,如下:
- unfulfilled/pending
- fulfilled/resolved
- failed/rejected
状态的转换只能由 pending 到 resolved,或者是 pending 到 rejected。关键点在于,状态一旦转变,是不可更改的。详细的实现机制,有太多的文章在解释了,大多数都是从 Promise 这个状态机的特性出发,进行实现的,我就不在这里啰嗦了。api 的基本使用,MDN 上面很清楚,我们主要来了解一下遇到 Promise 的注意点,和我们用 Promise 能做哪些有趣的事情呢?
注意点
new Promise()
会立即执行,而且是无法取消的;- Promise 不会出现回调函数那种错过监听就得不到结果的情况,只要你有 Promise 这个对象了,随时可以
.then()
它; new Promise()
返回一个 Promise 对象,.then()
方法返回的也是 Promise 实例,但是是一个新的 Promise 实例,不是原来那个了哦,因此,可以有下面这种链式写法:
实用举例
1. 异步图片加载
2. 延时函数
3. api 请求的统一封装
ok,最后再啰嗦一个点,关于 Promise.all()。
Promise.all
里的任务列表[asyncTask(1),asyncTask(2),asyncTask(3)]
,是按顺序发起的,由于它们都是异步的,互相之间并不阻塞,每个任务完成时机是不确定的。尽管如此,所有任务结束之后,它们的结果仍然是按顺序地映射到resultList
里,这样就能和Promise.all里的任务列表[asyncTask(1),asyncTask(2),asyncTask(3)]
一一对应起来了。