浅析Promise

230 阅读4分钟

什么是Promise?

Promise是ES6提供的一种解决异步编程的方案。

Promise的本质是一个构造函数,我们可以使用new Promise来创建Promise实例对象,利用Promise对象的方法进行异步编程。


为什么要使用Promise?

   1. Promise定义回调函数的方式更灵活。

使用纯回调函数解决异步问题,回调函数必须在一开始就被指定,而Promise的回调函数可以在启动了异步任务后指定,甚至可以在异步任务结束后指定。

   2. Promise可以解决回调函数因为嵌套造成的回调地狱。

如果后面的异步操作,需要拿到前面异步操作的数据后才能执行,使用纯回调函数,会出现层层嵌套的问题。造成的结果是代码不易阅读,不易维护。

Promise的三种状态——pending、resolved、rejected

新建一个Promise对象时,初始的状态为pending(等待中),调用resolve(),状态变为resolved/fullfilled(成功)。调用reject(),状态变为rejected(失败)。

状态一旦改变,就无法再改变。也就是说,如果已经调用了resolve()和reject()中任意一个函数,再进行调用,Promise状态不会再发生改变。


Promise的执行流程


简述:一开始,我们通过new Promise()创建一个Promise对象,此时的状态为pending。promise的参数接收两个函数,分别是resolve和reject。在成功的时候调用resolve(),在失败的时候调用reject()。resolve()和reject()中的任意一个被调用后,promise的状态发生对应的改变(resloved/rejected)。resolve()和reject()的调用是promise状态的标识,也是后续异步操作的指路灯。只有在状态确认后,才能进行对应的成功/失败的异步操作,也就是执行then/catch中的回调函数。执行完回调函数后,会返回一个新的Promise对象。


Promise代码例子

const promise=new Promise((reslove,reject)=>{  //执行器函数(executor)--> 同步回调函数
    setTimeout(()=>{
        if(Date.now()%2==0){
            //成功时调用resolve()
            reslove('success '+Date.now());  //reslove和reject中都可以传入值,且只能传入一个值
        }else{
            //失败时调用reject()
            reject('failed '+Date.now());
        }
    },1000)
})

// then和catch会接收在resolve和reject中传入的数据
//1. then可以接收resolve和reject的值(resolve在前,reject在后)
promise.then(value=>{
    console.log(value);  //onresolved回调函数  这里的value接收的就是'success '+Date.now()
},reason=>{
    console.log(reason);  //onrejected回调函数  这里的reason接收的就是'failed '+Date.now()
})

//2. catch只能接收reject的值
promise.then(value=>{
    console.log(value); 
}).catch(reason=>{
    console.log(reason);
})

//3. 也可以只接收成功/失败的值
promise.then(value=>{
    console.log(value); 
})
promise.then(null,reason=>{
    console.log(reason); 
})


promise中是先确定状态还是先确定回调?

我们可以通过定时器的方法人为地改变状态和回调的执行顺序,不管怎样都是可以执行的。但是在程序执行的过程中,永远都是先确认状态,再执行回调函数的。


JavaScript的执行机制宏任务和微任务 与 Promise

我们都知道,JavaScript的执行是单线程的,同步任务会率先一件一件得在主线程完成,而异步任务会进入任务队列,等待同步任务完成后执行。

而任务对列中还分为了宏任务和微任务。

宏任务:script,DOM事件函数,ajax,定时器等。

微任务:promise等。

Promise作为典型的微任务,需要注意的是new Promise中的回调函数(也就是执行器函数)是立即执行的,也就是说执行器函数是一个同步回调函数。而promise.then中回调函数才是异步任务(微任务)

还有一点需要注意的是,promise.then中的回调函数必须在promise状态确定之后才能执行。下面是一个例子:

const promise1 = new Promise(function(resolve, reject) {
    console.log(3);
    setTimeout(function(){
        resolve('Success!');
        console.log(1);
    },1000)
});

promise1.then(function(value) {
    console.log(value);
    console.log(2);
});

//代码输出结果:3 (一秒后) 1 success!2


代码开始运行后,先从最大的script宏任务开始,new promise直接执行,输出3,setTimeout放入宏任务对列,紧接着的then被放入微任务队列,而本该先执行的微任务,却反而在宏任务结束之后才执行,这里的执行顺序显然是不符合js的执行机制的。

现在,在这段代码里添加一行代码。

const promise1 = new Promise(function(resolve, reject) {
     console.log(3);
     setTimeout(function(){
         resolve('Success!');
         console.log(1);
     },1000)
     resolve('haha');  //我是那行被添加的代码
 });

promise1.then(function(value) {
    console.log(value);
    console.log(2);
});

//代码输出结果:3 haha 2 (一秒后)1

这时,输出的结果就是then在前,setTimeout在后了。这行代码改变的是promise的状态。也就是说,then执行的前提是promise的状态已经被确定

在没加代码前,虽然在执行机制中then已经可以执行,但是由于promise的状态未确定,导致then无法执行(then很苦恼,到底应该执行成功的回调还是失败的回调呢?),没办法,只好等待定时器结束状态确定下来再执行了。加了这行代码后,状态已经确定,then自然就可以在setTimeout前执行了。并且由于状态一旦确定不能更改,所以setTimeout中的resolve('Success!')已经名存实亡了。