ES6Promise详细解析
一:Promise出现的原因
在说到promise之前不得不提到js中的事件循环,只有了解了事件循环才能去理解promise
js有一个特性那就是单线程,而单线程的优点有:不会有线程的冲突,缺点就是一个字:慢
我们如果想要对js进行一个多线程操作该怎么办呢?那当然就是js的异步操作了,异步操作有一个核心的体系,那就是 事件循环
1:事件循环
(1)js的异步函数:在js中有一些函数他不会立刻去执行,而是在符合了一些条件之后才会去执行,而这类函数我们称为异步函数,例如 : 事件的处理,ajax请求,定时器。。。而这些异步的函数是个怎么运行方式呢?他们又是在那个地方运行的呢?
执行栈
执行栈可以理解为是一种结构,而这个结构里面放的就是函数的执行环境,在每一次函数执行前,函数的所有内容都会放进执行栈当中,在函数调用之前,先创建一个执行环境,放入执行栈当中,在函数调用完成的时候,就会销毁执行环境,反复都是如此
浏览器宿主环境
js的主线程在碰到异步操作函数的时候,就会把这些函数抛给相对应的线程,然后继续去执行同步的函数
浏览器宿主环境的五个线程:
- js引擎(js主线程)
- GUI线程/布局线程(和js引擎是相互等待的关系也就是说两者只会有一个运行)
- 事件监听线程
- 计时线程
- 网络线程
而当2 3 4 5线程发生了一些事情之后,就会去处理这些事情,而处理的方法就是将这些需要处理的函数放在事件队列(也就是内存)当中。
而他们什么时候会去执行呢?就是当js引擎(主线程)发现执行栈内没有可运行的内容的时候,就会去事件队列中去拿一个函数加入到自己的执行栈内进行执行
这样就形成了一个事件的循环
那么问题又来了,事件队列中那么多的等待执行的函数到底先执行哪一个呢?
事件队列中有一个进出方式那就是先进先出,你先把它放进来那就先让他执行完了丢出去
重点来了!
事件队列也会分为两种
1.宏队列 : 定时器的回调,事件的回调,http的回调也就是说普通的异步操作都是放在宏队列当中让他们一个个排好队等着执行
2.微队列 : 简单点可以理解成它就是队列中的VIP,你放一个进来我就直接给你插队第一个执行了,让普通的异步继续排队,而微队列中只会有两种情况才会存进来 (1)MutationObserver() (监听DOM树的变化,当DOM树发生改变的时候执行) (2)promise对象
2:回调地狱
我们来模拟一个简单的例子去描述回调地狱
//点击按钮一后给按钮二绑定点击事件,点击按钮二给按钮三绑定点击事件
<button id="btn1">:给btn2绑定点击事件</button>
<button id="btn2">给btn3绑定点击事件</button>
<button id="btn3">点击后弹出HelloKitty</button>
<script>
const btn1 = document.getElementById("btn1")
const btn2 = document.getElementById("btn2")
const btn3 = document.getElementById("btn3")
btn1.onclick = ()=>{
btn2.onclick = ()=>{
btn3.onclick = ()=>{
alert("HelloKitty")
}
}
}
</script>
从上个例子我们发现了,只有一层层点击之后才能得到弹出HelloKitty,不按照顺序来是实现不了的,并且形成了一层层回调嵌套,而当异步操作的回调函数嵌套过多之后就形成了回调地狱,而promise的出现就是为了解决回调地狱的问题
二:Promise的运行阶段
当一件事情可以发生异步操作的时候,又分成了两个阶段 unsettled(未决阶段)和 settled (已决阶段) 事情的走向是从未决阶段走向已决阶段,并且未决阶段可以控制已决阶段,从而决定事情的最终走向
程序也分为了三个状态:
1.pending : 等待状态,处于未决状态当中,表示事情还在等待中没有出现结果
2.resolved : 已处理状态 ,处于已决阶段当中,表示已经出现了正常的结果
3.rejected : 已拒绝状态,处于已决状态当中,表示已决出现了错误信息的结果
resolved 这是一个正常的已决阶段下的状态,后续处理为thenable
rejected 这是一个非正常的已决阶段下的状态,后续处理为catchable
无论是阶段还是状态都是不可逆的
三:Promise的使用
promise从本质来说就是一个构造函数,所以说需要new出来
conts pro = new Promise((resolve,reject)=>{
这里面的代码都属于未决阶段
resolve() 这里就是通过resolve函数把promise从未决阶段推向已决阶段状态就是resolveed
reject() 这里就是通过reject函数把promise从未决阶段推向已决阶段的rejected
在一个promise中resolve和reject只能存在一个,如果有多个只会第一个有效
这里面是可以传递参数的,可以是任意的数据类型,表示推向到状态的数据
})
有推送就肯定有接收
pro.then(resolve=>{ 这里就是已决的阶段了
这里的resolve就是推送过来的正常数据
},err=>{ 这是一个可选的函数
这里的err就是推送过来的错误信息数据
})
下面是一个简单的应用
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms, 'done');
});
}
timeout(100).then((value) => {
console.log(value);
});
timeout方法返回一个Promise实例,表示一段时间以后才会发生的结果。过了指定的时间(ms参数)以后,
Promise实例的状态变为Resolved,就会触发then方法绑定的回调函数。
注意一点在promise新建后里面的输入等语句是同步的,只有当使用then运行才是加入到微队列中的异步操作
let promise = new Promise(function(resolve, reject) {
console.log('Promise'); //同步的立即执行
resolve();
});
promise.then(function() { //异步执行(加入到微队列中)
console.log('Resolved.');
});
console.log('Hi!'); //这里也是同步
// Promise
// Hi!
// Resolved
四:Promise的方法
resolve()
resolve就是创建promise的一种简写
const pro = Promise.resolve(1) //表示正确信息
pro.then(resolve=>{
console.log(resolve) // 1
})
reject()
reject也是是创建promise的一种简写
const pro = Promise.reject(1) //表示错误信息
pro.then(err=>{
console.log(err) // 1
})
all(arr)
这个方法会返回一个新的promise对象,如果里面所有的promise对象都成功了才会触发,一旦有一个失败
则该promise失败
const arr = [];
for(let i = 0;i<10;i++){
arr.push(new Promise((resolve,reject)=>{
setTimeout(()=>{
if(Math.random() < 0.9){
console.log(i,"成功")
resolve(i)
}else{
console.log(i,"失败")
reject(i)
}
})
}))
}
const pro = Promise.all(arr)
pro.then(data=>{
console.log("全部成功",data)
},err=>{
console.log("有失败的",err)
})
race(arr)
返回一个全新的promise对象,当数组中任意一个promise对象完成时,就马上回去使用完成好的promise对象的
结果,不管这个结果是成功还是失败
const arr = [];
for(let i = 0;i<10;i++){
arr.push(new Promise((resolve,reject)=>{
setTimeout(()=>{
if(Math.random() < 0.9){
console.log(i,"成功")
resolve(i)
}else{
console.log(i,"失败")
reject(i)
}
})
}))
}
const pro = Promise.race(arr)
pro.then(data=>{
console.log("成功",data)
},err=>{
console.log("失败",err)
})