liao一下promise
先来一碗毒鸡汤润润喉:
不要以为世界抛弃了你,世界根本没空搭理你!吼吼~
promise /ˈprɒmɪs/ 【许诺,承诺】
关于promise之前也有各种开源的写法,在ES6中被规范化,更加的受广大前端开发工程师所喜爱。
ES6 规定,Promise对象是一个构造函数,用来生成Promise实例。我们通过创建该构造函数的对象来执行异步操作。嗯~大概就是酱紫:
let pp = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('ok!!');
}, 2000);
}).then(resolve){ // 异步函数执行成功
console.log(resolve);
}.catch(reject){ // 异步函数执行失败
console.log(reject);
};
分开看就是先创建一个实例,然后调用实例继承的Promise类的方法then和catch等。
let pp = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('ok!!');
}, 2000);
});
// 执行异步函数成功
pp.then((resolve) => {
console.log(resolve);
});
// 异步函数执行失败
pp.catch((reject) => {
console.log(reject);
});
这就是promise的基本写法。
很疑惑的是都说promise是实现异步的函数,那为啥在promise里面都要再写个异步的setTimeout之类的函数呢?那么问题来了,promise里面的内容,真的是异步吗?我们做个小小的例子看一下:
console.log(111111);
let pp = new Promise((resolve, reject) => { // 定义一个promise的实例对象
console.log(222222);
});
console.log(333333);
假如promise是异步的,那么执行结果,必然是:111111 =》 333333 =》 222222。我们执行一下,看执行结果:
竟然是乖乖的顺序执行的,也就是说promise是同步的!!
那么问题又来了,为啥大家都说promise是实现异步的呢?
看例子:
let pp = new Promise((resolve, reject) => {
resolve(555555);
});
console.log(444444)
pp.then((resolve) => {
console.log(resolve);
});
console.log(666666);
直接看结果:
说明了什么?promise的then是异步的,也就是说真正实现异步的是then()!!
所以分析一哈,promise的内容是顺序执行的,调用then方法的时候才真正的实现了异步。而在实际开发过程中,我们一般会在promise中执行比较复杂的函数比如调用接口之类的,then用来处理回调。所以为了提高效率,我们普遍会在promise内部使用异步函数。
关于promise:
promise有三个状态:pending,fulfilled,rejected
用白话说就是promise里面定义的内容的执行状态,也可以理解为resolve的结果的完成状态。
- pending /ˈpendɪŋ/ 【待定,悬而未决】在这里,我们理解为正在执行中。
- fulfilled /fʊlˈfɪld/ 【实现,履行】在这里,我们理解为执行成功。
- rejected /rɪˈdʒektɪd/ 【拒绝接受,不予考虑】在这里,我们理解为执行失败了。
promise有且只有上面三种状态,我们可以根据这几种状态的含义,很清晰的能分辨出状态的变换过程,只有两种:
- pending =》fulfilled :执行成功,resolve被赋值。
- pending =》rejected:执行失败,rejected被赋值。
而且这两种状态的变化是不可逆的,一旦发生改变后,状态就凝固了,我们叫它resolved(已定型)。也就是说resolve或者reject被赋值之后,就固定了,不会再变化了,后续调用promise对象的then或catch方法时,只是拿resolve或reject的值进行一些需要的操作而已。
比如我们把promise的回调函数写在一个click事件里面:
let i = 0;
let pp = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(i++);
}, 2000);
});
let btn = document,getElementById('button');
btn.onclick = function () {
pp.then((resolve) => {
console.log(resolve);
});
};
我们知道这个id为button的按钮每点击一次,必然会在控制台上打印一次,但是结果是0,1, 2,3,4吗?并不是,如果你真实的实践一次,会发现每次点击按钮,打印的都是0!
这就用事实论证了上面状态不可逆的问题。执行了就是执行了,绝不会再执行一次,resolve被赋值后会一直给你保留着,随时都可以使用。就是说无论你什么时候调用then的时候,都可以拿到这个resolve的值。
现在,知道了promise的三种状态的变化,我们再来分析一下promise常用的方法:then(),catch(),all(),race()。
在上面我们讲解的过程中,一直在调用then和catch这两个方法,这两个方法也是我们在使用promise的过程中出现最多的两个方法,意思就是执行成功和执行失败的回调函数,这里就不多说了。
主要说一下all() 和 race()
- all():顾名思义,all 全部。之前我们理解的then()是一个promise执行成功的回调,那如果我想要多个promise都执行成功后再进行回调,all就派上用场了。用法如下:
function p1 () {
return new Promise((resolve) => {
console.log('p1');
resolve('p11');
});
}
function p2 () {
return new Promise((resolve) => {
console.log('p2');
resolve('p22');
});
}
function p3 () {
return new Promise((resolve) => {
console.log('p3');
resolve('p33');
});
}
let btn = document,getElementById('button');
btn.onclick = function () {
Promise.all([p1(), p2(), p3()]).then((results) => {
console.log(results);
});
};
结果:
p1
p2
p3
[“p11”, “p22”, “p33”]
综上所述:all函数是由Promise类调用,且参数为数组,数组元素为promise对象,回调函数中参数为所有promise的resolve值组成的数组。嗯嗯~就酱紫。
- race():用法与all相同,含义与all相反,all是等所有promise全部执行成功后,才执行回调函数,而race是只要有一个promise对象执行成功就执行回调,用法如下:
let pp1 = new Promise(resolve => {
setTimeout(() => {
console.log('p1');
resolve('p11');
}, 500);
});
let pp2 = new Promise(resolve => {
setTimeout(() => {
console.log('p2');
resolve('p22');
}, 200);
});
Promise.race([pp1, pp2]).then((results) => {
console.log(results);
});
执行结果:
p2
p22
p1
根据setTimeout可以看出p2会优先于p1先执行完成,所以race会在p2执行完成后,直接出发回调,打印出的结果为:p2 和 p22
然后,因为pp1已经定义了且promise是同步的,所以p1也会在500毫秒延时结束时,成功的打印出来。
到这里,我们对promise的理解应该就比较透彻了, 三种状态,两种状态变化,四个方法这三个重点理解透彻了,就很简单了。
拓展
其实提到异步,脑海中蹦出了一堆东西,像什么setTimeout,setInterval什么的,现在我们来分析一下promise和setTimeout之间的一些小猫腻。
let pp = new Promise((resolve) => { // 定义一个promise独对象
resolve(123456);
});
setTimeout(() => {
console.log('setTimeout 的异步执行!');
}, 0);
pp.then(resolve => {
console.log('promise 的异步!');
});
你猜,你猜,结果是谁先?
执行结果:
promise 的异步!
setTimeout 的异步执行!
神奇不?经过上边的分析,我们知道promise的then是异步的,而setTimeout也是异步的,且我们设置的延时时间为0。
按道理说,大家都是异步也的按执行完成的时间顺序来吧。为啥promise的then有优先权呢?
这就要从JavaScript的异步原理说起了,都说JavaScript是单线程的,也就是说JavaScript有一个主线程task在处理逻辑。
setTimeout的异步呢,就是会重新创建一个新的线程task去执行异步的逻辑,叫做宏任务。
promise虽然也是异步,但它没有重新开启一个新的线程,而是一个微任务(micro task)。
大家都知道JavaScript的异步就是主线程执行结束后,去异步任务的回调队列中去查看,是否有执行完成的异步回调,如果有就执行。
这两种异步执行结束之后,都会乖乖的到任务队列中去排队,等待主线程的宠幸。不同点在于setTimeout的宏任务会到宏任务队列中去排队,而promise微任务会到微任务队列中去排队。
主线程会先宠幸自己的微任务,然后再宠幸宏任务。
所以promise会优先于setTimeout执行。这个还是要重点关注下的,现在很多公司都会用这个问题做为面试题,所以了解一下肯定没错的啦~