淺談Promise原理與應用

  在JavaScript中,全部代碼都是單線程。因爲該「缺陷」,JavaScript在處理網絡操做、事件操做時都是須要進行異步執行的。AJAX就是一個典型的異步操做javascript

 

  對於異步操做,有傳統的利用回調函數和使用 Promise,兩者的對好比下:java

//以往回調方式
函數1(function(){
    //代碼執行...(ajax1)
    
    函數2(function(){
        //代碼執行...(ajax2)
        
        函數3(function(data3){
              //代碼執行...(ajax3)
        });
        ...
    });
});

//Promise回調方式:鏈式調用,可構建多個回調函數。
//例如請求一個ajax以後,須要這個拿到這個ajax的數據去請求下一個ajax
promise().then().then()...catch()

  對比可知,使用傳統回調函數方式處理異步操做很複雜。爲了解決這樣的問題,commonJS引入了Promise概念,很好的解決了JavaScript的異步操做。ajax

概念編程

  Promise是異步編程的一種解決方案,比傳統的解決方案(回調函數和事件)更簡單易理解且實用,因此Promise簡單來講就是一個容器,裏面保存着某個將來纔會執行事件(一般爲一個異步操做)的結果。數組

特色promise

  1. 對象的狀態不受外界影響
  2. 一旦狀態改變,就不會再變化,任什麼時候候均可以獲得這個結果    

語法服務器

 

//建立Promise實例
    let promise = new Promise( (resolve,reject) =>{
        // 執行相應代碼並根據狀況調用resolve或者reject
        ...
    } )
    // 在promise的then方法中執行回調
    Promise.then( function(){
        // 第一個參數是返回resolve狀態時執行的回調函數
    },function(){
        // 第二個參數是返回reject狀態時執行的回調函數
    } )

 

狀態網絡

  Promise對象表明一個異步操做,有三種狀態異步

    pending(等待)resolved(成功狀態)rejected(失敗狀態)async

    兩種狀態改變方式:pending => resolved,pending => rejected

  注:只有異步操做的結果才能夠決定當前是哪種狀態,任何其餘操做都沒法改變這個狀態

缺點 

  1. 沒法取消 Promise,一旦新建他就會當即執行,中途沒法取消;
  2. 若是不設置回調函數,Promise 內部拋出的錯誤,不會反應到外部;
  3. 當處於 pending 狀態時,沒法得知目前進展到哪一階段(剛剛開始仍是即將完成)

用法   先看下面這個例子:

1  setTimeout( ()=>{
2         console.log('123');
3     },0 )
4     console.log('456');

 

  執行結果相信你們都知道,上面console處於異步代碼中(即便延遲爲0),下面console處於同步代碼中,若是想要 ‘456’ 在 ‘123’ 執行結束後再輸出呢?

 

  傳統回調函數方式:

1 setTimeout( ()=>{
2         console.log('123');
3         fn();
4     },0 )
5     function fn(){
6         console.log('456')
7     }
8   // 123
9   // 456

  使用 Promise 方式:

 1 let promise = new Promise( (resolve,reject) =>{
 2         setTimeout( ()=>{
 3             console.log('123');
 4             resolve('456');
 5         } ,0)
 6     } )
 7     promise.then(function(data){
 8         // resolve狀態
 9         console.log(data);
10     },function(error){
11         // reject狀態
12     })
13   // 123
14   // 456

  Promise 實例生成之後,能夠用 then 方法分別指定 resolved 狀態和 rejected 狀態的回調函數。也就是說,狀態由實例化時的參數(兩個不一樣狀態的回調函數)執行來決定的,根據不一樣的

狀態來執行具體哪一個函數。

  注:resolve() 和 reject() 的闡述會傳遞到對應的回調函數的 data 或者 error,且 then 返回的是一個新的 Promise 實例,也就是或還能夠繼續 then

 

鏈式操做用法   從 表面行看,咱們或許會以爲 Promise 只是可以簡化傳統的層層回調寫法。然而,Promise的精髓是 ‘狀態’,用維護狀態,傳遞狀態的方式來使得回調函數可以及時調用,它比傳遞 callback 函數要靈活、簡單的多。下面爲一個 Promise 的通常使用場景:

 1  async1()
 2     .then(function(data){
 3         console.log(data);
 4         return async2();
 5     })
 6     .then(function(data){
 7         console.log(data);
 8         return async3();
 9     })
10     .then(function(data){
11         console.log(data);
12     });
13 
14     function async1(){
15         let p = new Promise(function(resolve,reject){
16             // 異步操做
17             setTimeout(()=>{
18                 console.log('異步任務1執行完成!');
19                 resolve('數據1');
20             },1000);
21         });
22         return p;
23     };
24     function async2(){
25         let p = new Promise((resolve,reject)=>{
26             // 異步操做
27             setTimeout(()=>{
28                 console.log('異步任務2執行完成!');
29                 resolve('數據2');
30             },2000);
31         });
32         return p;
33     };
34     function async3(){
35         let p = new Promise((resolve,reject)=>{
36             // 異步操做
37             setTimeout(()=>{
38                 console.log('異步任務3執行完成!');
39                 resolve('數據3');
40             },3000);
41         });
42         return p;
43     }
44     // 1秒後...
45     // 異步任務1執行完成!
46     // 數據1
47     // 2秒後...
48     // 異步任務2執行完成!
49     // 數據2
50     // 3秒後...
51     // 異步任務3執行完成!
52     // 數據3

  在 then 方法中,能夠不用 return Promise實例對象,直接返回數據在後面的 then 中也可以接收到數據

reject用法   在前面的例子中只有 resolve(成功狀態)的回調,實際應用中還會有失敗狀態,reject 就是把 Promise 的狀態設置爲 rejected ,這樣咱們就可以在 then 中捕捉到,而後執行相應的回調

 1 let num = 10;
 2     let p1 = function(){
 3         return new Promise((resolve,reject)=>{
 4             if(num <= 5){
 5                 resolve("<=5,走resolve");
 6                 console.log("resolve不能結束Promise");
 7             }
 8             else{
 9                 reject(">5,走reject");
10                 console.log("reject不能結束Promise")
11             }
12         })
13     }
14 
15     p1()
16         .then(function(data){
17             console.log(data)
18         },function(error){
19             console.log(error)
20         })
21     // reject不能結束Promise
22     // >5,走reject

  resolve reject 永遠在當前環境的最後面執行,因此後面的同步代碼會先執行

  若是 resolve 和 reject 以後還有代碼須要執行,最好放在 then 裏,而後在 resolve 和 reject 前面寫上 return

Promise.prototype.catch()   Promise.prototype.catch() 方法是 .then( null, rejection ) 的別名,用於指定發生錯誤的回調函數

1 p1()
2   .then(function(data){
3     console.log(data)
4   })
5   .catch(function(err){
6       console.log(err)
7   })
8 //reject不能結束Promise
9 //>5,走reject     

Promise.all() 

  Promise.all() 方法用於將多個 Promise 實例包裝成一個新的 Promise 實例

1 const p = Promise.all( [p1,p2,p3] );

  p 的狀態由 p一、p二、p3 決定,分爲兩種狀況:

  1. 只有 p一、p二、p3 的狀態都爲 resolved 時,p 狀態纔會變成 resolved,此時 p一、p二、p3 的返回值組成一個數組傳遞給 p 的回調函數
  2. 只要 p一、p二、p3之中有一個狀態爲 rejected ,p 的狀態就變成 rejected,此時第一個狀態 爲 rejected 的實例的返回值會傳遞給 p 的回調函數

  因爲p 是包含3個 Promise 實例的數組,只有這三個實例狀態都爲 resolved,或者其中有一個及以上的實例狀態爲 rejected 時,纔會調用 Promise.all 方法後面的回調函數

  若是做爲參數的 Promise 實例本身定義了 catch 方法,那麼它一旦被 rejected,並不會觸發 Promise.all 的 catch 方法,若是沒有實例參數定義本身的 catch,就會調用 Promise.all 的 catch 方法

Promise.race()   Promise.race() 方法一樣是將多個 Promise 實例,包裝成一個新的 Promise 實例

1 const p = Promise.race( [p1,p2,p3] )

  使用該方法時,只要 p一、p二、p3 之中有一個實例率先改變狀態,p 的狀態就會跟着該實例變化,該實例的返回值傳遞給 p 的回調函數

Promise.resolve()   在實際應用中有時須要將一個對象轉爲 Promise 對象,Promise.resolve() 方法就可以實現,該實例的狀態爲 resolved

const promise = Promise.resolve( '123' );

// 等價於

Promise.resolve( '123' );
new Promise( resolve => resolve( '123' ) )

  Promise.resolve 方法的參數類型有四種狀況:

  • 參數爲一個 Promise 實例
    • 若是參數就是 Promise 實例,那麼 Promise.resolve 將不作任何修改,依然返回該實例
  • 參數爲一個 thenable 對象
    • thenable 對象指具備 then 方法的對象,如:
      let thenable = {
           then: function( resolve,reject ){
                resolve( 'aaa' );
      }  
      }
    • Promise.resolve 方法會將這個對象轉爲 Promise 對象,而後當即執行 thenable 對象的 then 方法
      let thenable = {
        then: function(resolve, reject) {
          resolve( 'aaa' );
        }
      };
      
      let p1 = Promise.resolve(thenable);
      p1.then(function(value) {
        console.log(value);  // aaa
      })

      上面代碼中,thenable 對象的 then 方法執行後,對象 p1 的狀態就變爲 resolved,從而當即執行後面那個 then 方法指定的回調函數,輸出 aaa

  • 參數不是具備 then 方法的對象,或者根本不是一個對象
    • 若是參數是一個原始值,或者不是一個具備 then 方法的對象,則 Promise.resolve 方法返回一個新的 Promise 對象,狀態爲 resolved
      1 const p = Promise.resolve('Hello');
      2 
      3 p.then(function (s){
      4   console.log(s)
      5 });
      6 // Hello

      上面代碼生成一個新的 Promise 對象的實例 p,因爲字符串 Hello 不屬於異步操做( String 對象不具備 then 方法),返回 Promise 實例的狀態生成就爲 resolved,因此回調函數會當即執行,且 Promise.resolve 方法的參數會同時傳給回調函數

  • 參數爲空
    • Promise.resolve 方法能夠不帶參數使用,此時直接返回一個 resolve 狀態的 Promise 對象
    • 若是但願獲得一個 Promise 對象,比較方便的方法就是直接調用不帶參數的 Promise.resolve 方法
      1 const p = Promise.resolve();
      2 
      3 p.then(function () {
      4   // ...
      5 });

       

    • 上面變量 p 就是一個 Promise 對象,注:當即 resolve 的 Promise 對象,是在本輪「事件循環」(event loop)的結束時,而不是在下一輪「事件循環」的開始時

       1 setTimeout(function () {
       2   console.log('three');
       3 }, 0);
       4 
       5 Promise.resolve().then(function () {
       6   console.log('two');
       7 });
       8 
       9 console.log('one');
      10 
      11 // one
      12 // two
      13 // three

      上面代碼中,setTimeout(fn,0)在下一輪「事件循環」開始時執行,Promise。resolve() 在本輪執行,console.log('one')則是當即執行,所以最早輸出

Promise.reject() 

  Promise.reject( reason ) 方法也會返回一個新的 Promise 實例,該實例的狀態爲 rejected

1 const p = Promise.reject('出錯了');
2 // 等同於
3 const p = new Promise((resolve, reject) => reject('出錯了'))
4 
5 p.then(null, function (s) {
6   console.log(s)
7 });
8 // 出錯了

  上面代碼生成一個 Promise 對象的實例 p,狀態爲rejected,回調函數會當即執行

  注:Promise.reject() 方法的參數,會原封不動的做爲 reject 的理由,變成後續方法的參數。這一點與 Promise.resolve 方法不一致

 1 const thenable = {
 2   then(resolve, reject) {
 3     reject('出錯了');
 4   }
 5 };
 6 
 7 Promise.reject(thenable)
 8 .catch(e => {
 9   console.log(e === thenable)
10 })
11 // true

  上面代碼中, Promise.reject 方法的參數爲一個 thenable 對象,執行之後,後面 catch 方法的參數不是 reject 拋出的 ‘出錯了’ 這個字符串,而是 thenable 對象

 

兩個附加的方法 

  ES6 的 Promise 提供的 API 不是不少,有些有用的方法能夠本身部署,下面介紹兩個不在 ES6 中,但頗有用的方法

done() 

  Promise 對象的回調鏈,無論以 then 方法或者 catch 方法結尾,要是最後一個方法拋出錯誤,都有可能沒法捕捉到(Promise內部的錯誤不會冒泡到全局)。所以,能夠提供一個 done 方法,老是處於回調鏈的末端,保證拋出任何可能出現的錯誤被捕捉

1 asyncFunc()
2   .then(f1)
3   .catch(r1)
4   .then(f2)
5   .done();

 

實例:

1 Promise.prototype.done = function (onFulfilled, onRejected) {
2   this.then(onFulfilled, onRejected)
3     .catch(function (reason) {
4       // 拋出一個全局錯誤
5       setTimeout(() => { throw reason }, 0);
6     });
7 };

  上面代碼可見,done 方法的使用,能夠像 then 方法那樣用,提供 resolved 和 rejected 狀態的回調函數,也能夠不提供任何參數。總之,done 都可以捕捉到任何可能出現的錯誤,並向全局拋出

 

finally() 

  finally 方法用於指定無論 Promise 對象最後狀態如何,都會執行的操做。它與 done 方法最大的區別是 finally 方法可以接收一個普通的回調函數做爲參數,該函數無論怎樣都必須執行

實例:服務器使用 Promise 處理請求,而後使用 finally 方法關掉服務器

1 server.listen(0)
2   .then(function () {
3     // run test
4   })
5   .finally(server.stop);
1 Promise.prototype.finally = function (callback) {
2   let P = this.constructor;
3   return this.then(
4     value  => P.resolve(callback()).then(() => value),
5     reason => P.resolve(callback()).then(() => { throw reason })
6   );
7 };

  上面代碼中,無論前面的 Promise 是resolved 狀態仍是 rejected 狀態,都會執行回調函數callback

相關文章
相關標籤/搜索