promise详解

1、promise

1.1、为什么用promise

主要为了解决回调地狱的问题

异步结构不清晰,promise可以让异步操作结构变得很清晰

1.2、promise语法

executor是带有 resolvereject 两个参数的函数 。Promise构造函数执行时立即调用executor 函数, resolvereject 两个函数作为参数传递给executor(executor 函数在Promise构造函数返回所建promise实例对象前被调用)。resolvereject 函数被调用时,分别将promise的状态改为fulfilled(完成)或rejected(失败)。executor 内部通常会执行一些异步操作,一旦异步操作执行完毕(可能成功/失败),要么调用resolve函数来将promise状态改成fulfilled,要么调用reject 函数将promise的状态改为rejected。如果在executor函数中抛出一个错误,那么该promise 状态为rejected。executor函数的返回值被忽略。

简单理解上面一段话,new Promise()里面接收一个函数,这个函数会理解执行,函数里面有两个参数resolve和reject,函数的执行体里面是异步的操作,异步操作有成功有失败

  • 成功:状态pending->fulfilled,并且用resolve 去接收成功的值
  • 失败:状态pending->rejected,并且用reject 去接收失败的值
  • 3种状态两种结果
new Promise( function(resolve, reject) {...} /* executor */  );

简单例子

 let p1=new Promise(function(resolve,reject){
           setTimeout(function(){
               let num=new Date().getTime();
               num%2==0?resolve('成功'):resolve('失败')
           },0)
        })
        p1.then(function(value){
            console.log(value)
        },function(reason){
            console.log(reason)
        })

连写

 let p1=new Promise(function(resolve,reject){
           setTimeout(function(){
               let num=new Date().getTime();
               num%2==0?resolve('成功'):resolve('失败')
           },0)
        }).then(function(value){
            console.log(value)
        },function(reason){
            console.log(reason)
        })

如果一个promise即调用了resolve,又调用了reject,谁先调用,最后就走对应的方法,

 new Promise(function (resolve, reject) {
            resolve("成功")
            reject("失败")
            console.log("执行了")
        }).then(value => {
            console.log(value)
        }, reason => {
            console.log(reason)
        })
        // 执行了
        // 成功

如果没有成功,刚在then的第二个参数写的失败的回调函数,其实也可以用catch

 new Promise(function (resolve, reject) {
           reject("失败")
            resolve("成功")
        })
        .then(value => {
            console.log(value)
        })
        .catch(reason => {
            console.log(reason)
        })
        // 失败

1.3、Promise.resolve()

成功的语法糖

let p1=new Promise(function(resolve,reject){
            resolve(11)
        })
        p1.then(function(value){
            console.log(value)
        })

Promise.resolve()

const p1=Promise.resolve(11); //跟上面是一样的
p2.then(value=>{console.log(value)})

1.4、Promise.reject()

失败的语法糖

const p3=Promise.reject(33)
p2.then(null,reason=>{console.log(reason)})

1.5、Promise.All()

Promise.All():发送了多个请求,只有全部成功才走成功的处理,只要其中有一个失败就失败,这个返回的是p2的原因

  • 失败原因,走第一个失败的原因
 const p1 = Promise.resolve(11); //跟上面是一样的
        const p2 = Promise.reject(22)
        const p3 = Promise.reject(33)
    
        const pAll = Promise.all([p1, p2, p3])
        pAll.then(
            value => {},
            reason => {
                console.log(reason)
            }
        )
  • 成功是几个结果组成的数组,结果顺序跟[p1, p2, p3]一样
 const p1 = Promise.resolve(11); //跟上面是一样的
        const p2 = Promise.resolve(22)
        const p3 = Promise.resolve(33)
    
        const pAll = Promise.all([p1, p2, p3])
        pAll.then(
            values => {
                console.log(values)
            }
        )
//[11,22,33]

1.6、Promise.race()

多个异步任务,谁先执行完就用谁的,可以用setTimeout延迟去模拟,这里我就不试了

const p1 = Promise.resolve(11); //跟上面是一样的
        const p2 = Promise.resolve(22)
        const p3 = Promise.resolve(33)
    
        const pRace = Promise.race([p1, p2, p3])
        pRace.then(
            value => {
                console.log(value)
            }
        )

如果第一个执行完是一个失败的,那就走失败

 const p1 = Promise.reject(11); //跟上面是一样的
        const p2 = Promise.resolve(22)
        const p3 = Promise.resolve(33)
    
        const pRace = Promise.race([ p1,p2, p3])
        pRace.then(
            value => {
                console.log(value)
            },
            reason=>{
                console.log(reason)
            }
        )

1.7、promise理解

1.7.1、如何改变promise的状态?
    resolve(value): 如果当前是pendding就会变为resolved
    reject(reason): 如果当前是pendding就会变为rejected
    抛出异常: 如果当前是pendding就会变为rejected
//如果当前是pendding就会变为rejected,内部抛出也是这样
const p=new Promise((resolve,reject)=>{
  throw new Error('出错了')
})

const p = new Promise((resolve, reject) =>{
        //resolve(1)//promies变为resolved成功状态
        //reject(2)//promise变为rejected失败状态
        //throw new Error("出错了")//抛出异常promise变为rejected失败状态,reason 为抛出的error
        throw 3
    });
    p.then(
        reason => {console.log("reason:", reason)}//3
    )
1.7.2、一个promise指定多个成功/失败回调函数,都会调用吗?
	都会调用
 const p1=Promise.resolve('11')
        p1.then(value=>{
            console.log("第一次"+value)
        })
        p1.then(value=>{
            console.log("第二次"+value)
        })
        //第一次11
        //第二次11
1.7.3、改变promise状态和指定回调函数谁先谁后
   3.1、都有可能,正常情况下时先指定回调函数再改变状态,但也可以先改变状态再指定回调函数
   3.2、如何先改变状态再指定回调?
 	 3.2.1、在执行器中直接调用resolve()/reject()
     3.2.2、延迟更长时间才调用then()
new Promise((resolve, reject) =>{
        setTimeout(()=>{
            resolve(1)//后改变的状态(同时指定数据),异步执行回调函数
        }, 1000)    
    }).then(//先指定回调函数,保存当前指定的回调函数
        value => {console.log("value1:", value)}//value1: 1
    )
1.7.4、promise.then()返回的新的promise的结果状态由什么决定?

如果没有返回值也没有抛出错误,就走成功undefined

  4.1、简单表达:由then()指定的回调函数执行的结果决定
  4.2、详细表达:
    4.2.1、如果抛出异常,新promise变为rejected,reason为抛出的异常
    4.2.2、如果返回的是非promise的任意值,新的promise变为resolved,value为返回的值
    4.2.3、如果返回的是另一个新promise,此promise的结果就会成为新promise的结果 
new Promise((resolve,reject)=>{
            setTimeout(function(){
                resolve(11)
            },1000)
        })
        .then(value=>{
            console.log("第一次"+value)
        })
        .then(value=>{
            console.log("第二次"+value)
        })
        //第一次11
        //第二次undefined
 new Promise((resolve,reject)=>{
            setTimeout(function(){
                reject(11)
            },1000)
        })
        .then(value=>{
            console.log("成功第一次"+value)
        },reason=>{
         console.log("失败第一次"+reason)
        })
        .then(value=>{
            console.log("成功第二次"+value)
        },reason=>{
         console.log("失败第二次"+reason)
        })
        //失败第一次11
        // 成功第二次undefined

以下都是针对第二次then的结果

new Promise((resolve,reject)=>{
            setTimeout(function(){
                resolve(11)
            },1000)
        })
        .then(value=>{
            console.log("成功第一次"+value)
            // return 2  //成功第二次2
            // return Promise.resolve(2)  //成功第二次2
            // return Promise.reject(2)  //失败第二次2
            throw 3;  //失败第二次3



        },reason=>{
         console.log("失败第一次"+reason)
        })
        .then(value=>{
            console.log("成功第二次"+value)
        },reason=>{
         console.log("失败第二次"+reason)
        })
1.7.5、 promise如何串连多个操作任务
1、promise的then()返回一个新的promise,可以看成then()的链式调用
2、通过then的链式调用串连多个同步/异步任务
new Promise((resolve, reject) =>{
        setTimeout(()=>{
            console.log("执行任务1(异步)")
            resolve(1)
        },1000);
    }).then(
        value => {
            console.log("任务1的结果:", value)
            console.log("执行任务2(同步):")
            return 2
        }
    ).then(
        value => {
            console.log("任务2的结果():", value)
            return new Promise((resolve, reject)=>{
                setTimeout(()=>{
                    console.log("执行任务3(异步)")
                    resolve(3)
                },1000)
            })
        }
    ).then(
        value => {
            console.log("任务3的结果", value)
        }
    )
/*
执行任务1(异步)
任务1的结果: 1
执行任务2(同步):
任务2的结果(): 2
执行任务3(异步)
任务3的结果 3
*/
1.7.6、promise异常穿透
1、当使用promise的then链式调用时,可以在最后指定失败的回调
2、前面任何操作出了异常,都会传到最后失败的回调中处理

第一个走的失败的回调,但是失败的回调没有写,默认他会执行 reason=>{throw reason},到第三个时,由于第二个没有抛出异常,也没有返回值,所以走成功值为undefined

catch在后面也不会成功,因为第三个走的成功,所以不会执行catch

new Promise((resolve, reject) => {
            reject(1)
        }).then(
            value => {
                console.log("onResolveed1():", value)
                return Promise.reject(2)
            }
        ).then(
            value => {
                console.log("onResolveed2():", value)
                return 3
            },
            reason => {
                console.log("第二个失败"+reason)
            }
        ).then(
            value => {
                console.log("onResolveed3()", value)
            }
        ).catch(err=>{
            console.log("catch"+err)
        })
/*
  第二个失败1
  onResolveed3() undefined
*/

默认在catch后面的then函数,执行成功和失败回调和上面的规则是一样的

 new Promise((resolve, reject) => {
            reject(1)
        }).catch(err => {
            console.log(err)
        }).then(value => {
            console.log("成功"+value)
        }, reason => {
           console.log("失败"+reason)
        })
/*
1
成功undefined
*/

如果我不想执行后面then的函数呢?这就看下一个中断promise链

1.7.7、中断promise链?
1、当时用promise的then的链式调用时,在中间中断,不再调用后面的回调函数
2、办法:在回调函数中返回一个pedding状态的promise对象
 new Promise((resolve, reject) => {
            reject(1)
        }).catch(err => {
            console.log(err)
            return new Promise((resolve,rejuct)=>{}) //返回一个pending的promise
        }).then(value => {
            console.log("成功"+value)
        }, reason => {
           console.log("失败"+reason)
        })

2、async和await

2.1、async

1、函数的返回值为promise对象
2、promise对象的结果由async函数执行的返回值决定

只要加了async,返回一个promise,里面保存了状态,如果async函数成功,下面就走成功回调,如果是失败,就失败的回调函数函数

async function fn1() {
       return 1
}
let result=fn1();
console.log(result)
//Promise


async  function fn1() {
      return 1
}
fn1().then(value=>{
  console.log(value)
})

//1

2.2、await

该指令会暂停异步函数的执行,并等待Promise执行,然后继续执行异步函数,并返回结果。

1、await右侧的表达式一般为promise对象,但也可以是其他的值 
2、如果表达式是promise对象,await返回的是promise成功的值
3、如果表达式是其他值,直接将此值作为await的返回值

如果value的右边是promise,返回的是promise成功时候的值

如果value的右边是promise,返回的是promise失败时候的值,就用try catch来获取

如果value右边的不是promise,返回的是值本身

 function fn2() {
        return  new Promise((resolve,reject)=>{
           setTimeout(()=>{
               resolve(5)
           },10)
         })
    }
     async function fn1() {
          const value=await fn2();
          console.log(value)
    }
    fn1() //5

注意:

 await必须写在async函数中,但是async函数中可以没有await

 如果await的promise失败了,就会抛出异常,需要通过try...catch来捕获处理

   

3、 JS异步之宏队列与微队列

1、JS中用来存储执行回调函数的队列包含2个不同特定的队列
2、宏队列:用来保存带执行的宏任务,比如:定时器回调,DOM事件回调,ajax回调
3、微队列:用来保存待执行的微任务,比如:promise的回调,MutationObserver的回调
4、JS执行时会区别这2两个队列
    1、JS引擎首先必须先执行所有的初始化同步任务代码
    2、每次准备取出第一个宏任务前,都要将所有的微任务一个一个取出来执行

注意promise放在微任务里面,需要更改状态才会放到微任务里面,比如是从pending =》resolved

或者 pedding变为rejected 才会放到微队列

 setTimeout(()=>{
       console.log('settimeout')
   },0)
   Promise.resolve(1).then(value=>{
       console.log("promise"+value)
   })
   
   /*
      promise1
      settimeout
   */

js会把程序走一遍,定时器加到宏队列,promise加到微队列,

每次会先把微队列执行完在执行宏队列

 setTimeout(()=>{
       console.log('定时器1')
   },0)

   setTimeout(()=>{
       console.log('定时器2')
   },0)
   Promise.resolve(1).then(value=>{
       console.log("第一个promise"+value)
   })
   Promise.resolve(2).then(value=>{
       console.log("第二个promise"+value)
   })

/*
第一个promise1
第二个promise2
定时器1
定时器2
*/

这里看下是先执行 第三个promise 还是先执行 定时器2

 setTimeout(()=>{
       console.log('定时器1')
       Promise.resolve(3).then(value=>{
       console.log("第三个promise"+value)
   })
   },0)

   setTimeout(()=>{
       console.log('定时器2')

   },0)
   Promise.resolve(1).then(value=>{
       console.log("第一个promise"+value)
   })
   Promise.resolve(2).then(value=>{
       console.log("第二个promise"+value)
   })
  /*
    第一个promise1
    第二个promise2
    定时器1
    第三个promise3
    定时器2
  */

这个也很好理解,当执行第一个定时器时,就把promise添加到了微队列

执行定时器2的时候,这个时候把微队列的取出来执行,所以第三个promise 先执行 定时器2后执行

4、面试题

第一题

 setTimeout(()=>{
       console.log(1)
   },0)
   new Promise((resolve)=>{
       console.log(2)
       resolve()
   }).then(()=>{
       console.log(3)
   }).then(()=>{
       console.log(4)
   })

   console.log(5)
//25341

第一个看同步执行 25

当执行到第一个then时,上面已经有结果了 pedding变为resolved ,所以放到了微队列了

这时候执行第一个then,执行完了第二个then就有状态变化了 pedding变为resolved,这里也放到了微队列

最后执行定时器

第二题

  const first = () => (new Promise((resolve, reject) => {
            console.log(3);
            let p = new Promise((resolve, reject) => {
                console.log(7);
                setTimeout(() => {
                    console.log(5);
                    resolve(6)
                }, 0)
                resolve(1)
            })
            resolve(2)
            p.then((arg) => {
                console.log(arg)
            })
        }))

        first().then((arg) => {
            console.log(arg);
        });
        console.log(4);
//374125

第一步同步执行:执行第一个promise输出3,接着执行第二个promise里面接着输出7,

setTimeout(() => {
                    console.log(5);
                    resolve(6)
                }, 0)

定时器放在宏队列

往下执行

p.then((arg) => {
         console.log(arg)
 })
 //这个上面执行 resolve(1)的时候就有结果了,arg的值为1,有未成功-》成功的状态变化,所以这个加入到微队列里面[then2]
  resolve(2) //这句话代表了第一个promise的状态右不成功到成功
   first().then((arg) => {
            console.log(arg);
    });
//这个第一个promise的then方法的回调函数也放在队列里,现在队里 [then2(1),then2(2)]

现在执行console.log(4) ,目前执行第一遍输出的是 3,7,4

现在取出微队列 输出 1,2

最后执行定时器 输出5,那个定时器的resolve(6)没有任何意义,因为改变了一次就不能在改变状态了

所以随后输出的是3,7,4,1,2,5

第三题

记住一句话:状态只能改变一次,所以就resolve('success1');是有效的

const promise = new Promise((resolve, reject) => {
    resolve('success1');
    reject('error');
    resolve('success2');
});

promise.then((res) => {
    console.log('then:', res);
}).catch((err) => {
    console.log('catch:', err);
})
//then: success1

resolve 函数将 Promise 对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;

reject 函数将 Promise 对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

而一旦状态改变,就不会再变。
所以 代码中的reject('error'); 不会有作用。

Promise 只能 resolve 一次,剩下的调用都会被忽略。
所以 第二次的 resolve('success2'); 也不会有作用。

5、参考链接

MDN promise

MDN promise使用

promise详解

promise面试题

promise参考视频

原文地址:https://www.cnblogs.com/wlhappy92/p/11899633.html