es6 Promise 事件机制分析
最近在学习es6的Promise,其中涉及到了Promsie的事件执行机制,因此总结了关于Promise的执行机制,若有错误,欢迎纠错和讨论。
在阮一峰老师的书中《es6 标准入门》对Promise的基础知识做出了详细的介绍,在此就不一一介绍了,直接开始整体,将Promsie中关于事件执行机制的问题与大家分享。
1.Promsie对象的创建以及执行顺序
创建方式:new Promise(function(resolve,reject){ //...}
这种方式在阮老师的书中指出该函数一旦创建,其内部的匿名函数会自动执行,在这里我们可以认为这种方式相当于同步任务会直接执行。以下代码亦可证明。
new Promise(function(resolve,reject){
console.log('1');
});
console.log('2');
结果:

结论:从结果的顺序来看,的确new Promise会立即执行,执行完后再继续执行后面的同步任务。
2.Promsie对象中的then()方法
Promise中的then()会根据Promsie的状态执行相应的回调函数,方法中有两个参数,分别是回调成功和回调失败时执行的函数。该函数是个异步任务。在此补充下关于JS内部的机制。众所周知,JS是单线程的,也就意味着无法同时做好几件事情,只能一件一件的做。代码执行时分为同步任务和异步任务,同步任务会在当前主线程中完成,执行中将变量和对象放入堆中,方法调用运行压入执行栈,需要变量时从堆中获取。而异步任务又可以分为microtask(微任务)和macrotask(宏任务),可以简单的理解为任务队列,通常将键盘事件和鼠标事件以及setTimeout()等归为宏任务,而我们的Promsie的then()归为微任务,执行中遇到他们会将他们放入相应的任务队列中。
在主线程的任务完成后会立即查看microtask是否有任务需要执行,若有则会执行直到队列为空队列,若本身为空则会查看macrotask任务队列,执行该队列中的第一个命令,当执行完第一条命令后会再次查看microtask是否有代码需要执行,同理,有则执行至空队列否则再次查看macrotask。
注意:这里的过程相当于macrotask任务队列的任务执行一次,则microtask任务队列的任务会全部执行。microtask全部任务执行完才会再去执行macrotask的下一条命令。
new Promise(function(resolve,reject){
resolve('1');
}).then(function(value){
console.log(value);
}).catch(function(error){
console.log('出错了,',error);
});
console.log('2');
结果:

结论:从上述结果来看,代码会立即执行new Promise中的代码,此时Prosmie状态改为resolved,而后调用then()方法,此时将该函数放入mircotask队列中,向下继续执行同步任务,待同步任务执行完后再回来执行mircotask中的函数。
3.Promsie和setTimeout的执行机制分析
此处直接引入一个例子作为介绍
console.log('1111111');
setTimeout(() => {
console.log('4----');
new Promise((resolve) => {
console.log('5-----');
resolve();
}).then(() => {
console.log('6----')
})
})
new Promise((resolve) => {
console.log('22222');
resolve();
}).then(() => {
console.log('33333-----')
})
setTimeout(() => {
console.log('77777');
new Promise((resolve) => {
console.log('888');
resolve();
}).then(() => {
console.log('9999999')
})
})
代码分析:
分析之前我们可以先在自己的小本本上写2列事件任务分类,分别是microtask微任务和macrotask宏任务,等下我们在分析代码时候可以模拟引擎的执行顺序将其写入小本子上,便于我们分析。
回归正题,1.首先第一行执行输出打印任务,这个是同步任务没什么说的,直接输出;
2.下一行遇到了setTimeout()事件,先不管内部是什么函数,先将这个函数放入到我们的macrotask任务队列中第一行的位置等待执行;
3.又遇到了new Promise,之前我们讲到,可以把它看成是同步任务所以直接执行其内部的代码,首先打印输出,之后遇到resovle(),此时Promsie的状态改为resolved,并且值为‘22222’,之后调用then(),该方法是个异步任务,所以同样不管内部代码是什么,将其放入我们的microtask任务队列的第一行中等待执行;
4.之后又遇到了setTimeout(),同样不管其内部代码直接加入macrotask任务中;
此时我们的同步任务执行完毕,我们整理下我们刚刚写下的2个任务队列中都有什么函数,在microtask中,有一个then()方法的函数,在macrotask任务队列中有2个按照执行顺序加入的setTimeout()。
主线程任务执行后会直接去执行microtask的任务,所以下一步开始执行microtask任务队列中的函数,输出打印‘33333-----’,此时microtask任务队列中的任务队列执行完毕并且后面没有其他任务了,此时队列为空,这个时候会去执行macrotask的第一个函数,首先输出打印,遇到Promise对象直接执行,并且状态改为resolved,执行then(),将其函数加入到microtask中,此时该setTImeout执行完毕,这个时候会去再去看microtask是否为空,这个时候发现我们刚刚加入了一个指令,所以直接执行,执行完后microtask又没有任务了,这个时候再去执行macrotask的下一条任务,同样执行完再去执行我们刚刚再次向microtask加入的新任务,至此执行完毕。

大家可以看看是否和大家推到的一样。
4.关于resolve()参数的问题
在这里参数是非promise对象就不探讨了,调用resolve()和reject()就决定了promise对象的状态了。这里讨论下参数是promise对象的问题。首先,给出一段代码。
var p1=Promise.resolve();
var p2=Promise.reject('出错了');
var p3=new Promise(function(resolve,reject){
resolve(p1);
}).then(()=>console.log('p3状态为resolved'),()=>console.log('p3状态为reject'));
var p4=new Promise(function(resolve,reject){
resolve(p2);
}).then(()=>console.log('p3状态为resolved'),()=>console.log('p3状态为reject'));

阮老师在书中指出传入参数为Promsie对象时p1的状态会决定p2的状态。从上面的代码中可以看到,p1和p2直接决定了p3和p4的状态,不会管resolve(),也验证了阮老师的说法。另外,当resolve()改为reject()方法时,我们发现不管参数的状态是什么最后Promise对象的状态都会变为reject。
另外,参数为Promise对象时,引擎会先去获取参数的状态,获得状态在返回决定当前Promise对象的状态,而获取Promsie对象状态的过程我们可以认为是异步,也就是将此过程直接加入到microtask任务队列中。当参数是thenable对象时,会调用其中的then方法,该过程也可以看成是异步的。
var p1=Promise.resolve();
var p2=new Promise(function(resolve,reject){
resolve(p1);
});
// p2.then(()=>console.log(2));
console.log(p2);
setTimeout(()=>console.log(p2),3000);

我们发现此时p2的状态并没有直接变为resolved,而是pending,在延迟3s后再判断p2的状态,此时已经变成了resolved,这样证实了我们的想法。参数为thenable对象时也表现出同样的结果。下面代码。
var p1={
then(resolve,reject){
resolve();
}
}
var p2=new Promise(function(resolve,reject){
resolve(p1);
});
console.log(p2);
setTimeout(()=>console.log(p2),3000);

5.关于resovle(Promise.resolve())的异步执行机制
此处问题也是困扰我好久的一个问题。这里首先需要验证下Promise.resolve()是否是同步的。
var p1=Promise.resolve();
console.log(p1);
setTimeout(()=>console.log(p1),3000);

从结果来看,p1确实是同步执行的。并且该方法会返回一个新的Promise对象。回到正题,前面讲到resolve()参数是Promsie对象时候去查询参数状态是个异步的,但这个时候我们发现在查询之前参数的状态其实已经确定了,引擎只是去获取状态,而此时我们的resolve(Promise.resolve()),内部并非一个已经确定的Promise对象,而是一个待执行的命令,因此这里会分两步走,第一步,异步执行执行Promise.resolve()命令,此时将新产生的Promsie对象的状态确定下来,name这个时候该函数就变成了前面我们讲到的那个样式,这个时候再执行异步操作获取参数的状态。
下面分析下之前困扰我好久的那个例子:
new Promise((resolve, reject) => {
console.log("async1 start");
console.log("async2");
resolve(Promise.resolve());
}).then(() => {
console.log("async1 end");
});
new Promise(function(resolve) {
console.log("promise1");
resolve();
}).then(function() {
console.log("promise2");
}).then(function() {
console.log("promise3");
}).then(function() {
console.log("promise4");

除IE外均测试,其结果一致。这里按照我们刚才的思维分析,首先输出async1和async2,此时将Promise.resolve()压入microtask,继续向后执行,遇到新的Promise,输出promise1,改变状态此时将then()的函数压入microtask,这个时候主线程执行完毕,开始执行microtask,Promise.resolve()执行完毕,得到确定状态的新的Promise对象,此时resolve()为获取该Promsie状态将该过程加入到刚才的microtask任务队列中,这个时候任务队列有2个,一个是输出promise2的命令,其后面紧跟获取状态的命令,所以执行输出Promise2的命令,执行完毕后又将下一个then()方法中的Promise3命令添加到获取状态的后面,这个时候再去执行获取状态的命令,得到了状态执行resolve(),触发then(),将输出async end的命令添加到Promise3输出的后面,下一步执行输出Promise3,触发then(),将Promise4输出命令加入到输出async end命令后面,这个时候执行async end输出和Promise4输出,执行完毕。
至此,Promise中的异步机制总结完了,欢迎大家留言指正和批评,和大家一起进步。
es6 Promise 事件机制分析的更多相关文章
- java自定义事件机制分析
import java.util.ArrayList; import java.util.EventListener; import java.util.EventObject; import jav ...
- Spring 中的事件机制
说到事件机制,可能脑海中最先浮现的就是日常使用的各种 listener,listener去监听事件源,如果被监听的事件有变化就会通知listener,从而针对变化做相应的动作.这些listener是怎 ...
- Cocos2d-X3.0 刨根问底(七)----- 事件机制Event源码分析
这一章,我们来分析Cocos2d-x 事件机制相关的源码, 根据Cocos2d-x的工程目录,我们可以找到所有关于事件的源码都存在放在下图所示的目录中. 从这个event_dispatcher目录中的 ...
- Android笔记:触摸事件的分析与总结----TouchEvent处理机制
原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://glblong.blog.51cto.com/3058613/1559320 ...
- Qt 事件系统浅析 (用 Windows API 描述,分析了QCoreApplication::exec()和QEventLoop::exec的源码)(比起新号槽,事件机制是更高级的抽象,拥有更多特性,比如 accept/ignore,filter,还是实现状态机等高级 API 的基础)
事件系统在 Qt 中扮演了十分重要的角色,不仅 GUI 的方方面面需要使用到事件系统,Signals/Slots 技术也离不开事件系统(多线程间).我们本文中暂且不描述 GUI 中的一些特殊情况,来说 ...
- ApplicationEvent事件机制源码分析
<spring扩展点之三:Spring 的监听事件 ApplicationListener 和 ApplicationEvent 用法,在spring启动后做些事情> <服务网关zu ...
- PHP实现事件机制实例分析
PHP实现事件机制实例分析 内置了事件机制的语言不多,php也没有提供这种功能.事件(Event)说简单了就是一个Observer模式.实现起来非常easy.可是有所不同的是,事件的监听者谁都能够加, ...
- React 为什么要把事件挂载到 document 上 & 事件机制源码分析
前言 我们都知道 React 组件绑定事件的本质是代理到 document 上,然而面试被问到,为什么要这么设计,有什么好处吗? 我知道肯定不会是因为虚拟 DOM 的原因,因为 Vue 的事件就能挂载 ...
- ES6 Promise 全面总结
转载:点击查看原文 ES6 Promise对象 ES6中,新增了Promise对象,它主要用于处理异步回调代码,让代码不至于陷入回调嵌套的死路中. @-v-@ 1. Promise本质 Promise ...
随机推荐
- token的理解
今天学习了token,它的英文意思是令牌的意思.在我理解即像通行证一样,在用户登录成功系统后,会为这个用户颁发一个token,这样它去其他系统都免登录,因为有了这个令牌. token的生成我们可以用U ...
- 浅入浅出Lambda表达式
大家在开发中会经常看到也会经常使用lambda表达式. 园子里也有很多详解lambda表达式的文章,多是从横向来讲述. 但lambda表达式到底如何变成现在这个样子,表达式的形式到底代表什么含义,这些 ...
- CSS3 Day1 练习
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- python scrapy cookies 处理
def start_requests(self): cookies = 'anonymid=jcokuqwe................省略' # 首先是对cookies进行分割以;为节点 ook ...
- Asp.Net中索引器的用法
索引器定义类似于属性,但其功能与属性并不相同.索引器提供一种特殊的方法编写get和set访问器.属性可以像访问字段一样访问对象的数据,索引器可以使用户像访问数组一样访问类成员. 一.索引器特性 1.g ...
- tclsh 用法
set foo "a bc" # 定义变量 set b {$a}; # 转义 b的值为" $a " ,而不是变量结果 ; incr a ; # 数字的自增. 将 ...
- nmap - 网络扫描
NMap,Network Mapper 最早是Linux下的网络扫描和嗅探工具包 网络链接扫描; nmap -PT 192.168.1.1-111 # 先ping在扫描主机开放端口 nmap -O 1 ...
- unbind()清除指定元素绑定效果
定义和用法 unbind() 方法移除被选元素的事件处理程序. 该方法能够移除所有的或被选的事件处理程序,或者当事件发生时终止指定函数的运行. ubind() 适用于任何通过 jQuery 附加的事件 ...
- HDU 2066 一个人的旅行 最短路问题
题目描述:输入的第一行有三个数,T,S,D,T表示一共有多少条线路,S表示起点的个数,D表示终点的个数,接下来就是输入T条路的信息了,要你判断从多个起点中任意一个到多个终点中的任意的一个的最短距离是多 ...
- 第8月第16天 django pil
1. https://github.com/chaonet/forum/ sudo easy_install --find-links http://www.pythonware.com/produ ...