promise詳解

一、promise

1.一、爲何用promise

主要爲了解決回調地獄的問題html

異步結構不清晰,promise可讓異步操做結構變得很清晰面試

1.二、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函數的返回值被忽略。ajax

簡單理解上面一段話,new Promise()裏面接收一個函數,這個函數會理解執行,函數裏面有兩個參數resolve和reject,函數的執行體裏面是異步的操做,異步操做有成功有失敗數組

  • 成功:狀態pending->fulfilled,而且用resolve 去接收成功的值
  • 失敗:狀態pending->rejected,而且用reject 去接收失敗的值
  • 3種狀態兩種結果
new Promise( function(resolve, reject) {...} /* executor */  );

簡單例子promise

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,誰先調用,最後就走對應的方法,async

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

若是沒有成功,剛在then的第二個參數寫的失敗的回調函數,其實也能夠用catchide

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

1.三、Promise.resolve()

成功的語法糖函數

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

Promise.resolve()ui

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

1.四、Promise.reject()

失敗的語法糖

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

1.五、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.六、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.七、promise理解

1.7.一、如何改變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.二、一個promise指定多個成功/失敗回調函數,都會調用嗎?
都會調用
const p1=Promise.resolve('11')
        p1.then(value=>{
            console.log("第一次"+value)
        })
        p1.then(value=>{
            console.log("第二次"+value)
        })
        //第一次11
        //第二次11
1.7.三、改變promise狀態和指定回調函數誰先誰後
3.一、都有可能,正常狀況下時先指定回調函數再改變狀態,但也能夠先改變狀態再指定回調函數
   3.二、如何先改變狀態再指定回調?
     3.2.一、在執行器中直接調用resolve()/reject()
     3.2.二、延遲更長時間才調用then()
new Promise((resolve, reject) =>{
        setTimeout(()=>{
            resolve(1)//後改變的狀態(同時指定數據),異步執行回調函數
        }, 1000)    
    }).then(//先指定回調函數,保存當前指定的回調函數
        value => {console.log("value1:", value)}//value1: 1
    )
1.7.四、promise.then()返回的新的promise的結果狀態由什麼決定?

若是沒有返回值也沒有拋出錯誤,就走成功undefined

  4.一、簡單表達:由then()指定的回調函數執行的結果決定
  4.二、詳細表達:
    4.2.一、若是拋出異常,新promise變爲rejected,reason爲拋出的異常
    4.2.二、若是返回的是非promise的任意值,新的promise變爲resolved,value爲返回的值
    4.2.三、若是返回的是另外一個新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.五、 promise如何串連多個操做任務
一、promise的then()返回一個新的promise,能夠當作then()的鏈式調用
二、經過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.六、promise異常穿透
一、當使用promise的then鏈式調用時,能夠在最後指定失敗的回調
二、前面任何操做出了異常,都會傳到最後失敗的回調中處理

第一個走的失敗的回調,可是失敗的回調沒有寫,默認他會執行 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.七、中斷promise鏈?
一、當時用promise的then的鏈式調用時,在中間中斷,再也不調用後面的回調函數
二、辦法:在回調函數中返回一個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)
        })

二、async和await

2.一、async

一、函數的返回值爲promise對象
二、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.二、await

該指令會暫停異步函數的執行,並等待Promise執行,而後繼續執行異步函數,並返回結果。

一、await右側的表達式通常爲promise對象,但也能夠是其餘的值 
二、若是表達式是promise對象,await返回的是promise成功的值
三、若是表達式是其餘值,直接將此值做爲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來捕獲處理

   

三、 JS異步之宏隊列與微隊列

一、JS中用來存儲執行回調函數的隊列包含2個不一樣特定的隊列
二、宏隊列:用來保存帶執行的宏任務,好比:定時器回調,DOM事件回調,ajax回調
三、微隊列:用來保存待執行的微任務,好比:promise的回調,MutationObserver的回調
四、JS執行時會區別這2兩個隊列
    一、JS引擎首先必須先執行全部的初始化同步任務代碼
    二、每次準備取出第一個宏任務前,都要將全部的微任務一個一個取出來執行

注意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後執行

四、面試題

第一題

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'); 也不會有做用。

五、參考連接

MDN promise

MDN promise使用

promise詳解

promise面試題

promise參考視頻

相關文章
相關標籤/搜索