1. 程式人生 > >淺談 Promise 的用法

淺談 Promise 的用法

關鍵詞:Promise,resolve,reject,Prepending,Resolve,Reject,then,catch,all,race

所謂Promise,簡單說就是一個容器,裡面儲存著某個未來才會結束的事件(通常是一個非同步操作)的結果。從語法上說,Promise 是一個物件,從它可以獲取非同步操作的訊息。Promise 提供統一的 API,各種非同步操作都可以用同樣的方法進行處理。

Promise物件有以下兩個特點。
(1)物件的狀態不受外界影響。Promise物件代表一個非同步操作,有三種狀態:Pending(進行中)、Resolved(已完成,又稱 Fulfilled)和Rejected(已失敗)。只有非同步操作的結果,可以決定當前是哪一種狀態,任何其他操作都無法改變這個狀態。這也是Promise這個名字的由來,它的英語意思就是“承諾”,表示其他手段無法改變。
(2)一旦狀態改變,就不會再變,任何時候都可以得到這個結果。Promise物件的狀態改變,只有兩種可能:從Pending變為Resolved和從Pending變為Rejected。只要這兩種情況發生,狀態就凝固了,不會再變了,會一直保持這個結果。就算改變已經發生了,你再對Promise物件添加回調函式,也會立即得到這個結果。這與事件(Event)完全不同,事件的特點是,如果你錯過了它,再去監聽,是得不到結果的。
有了Promise物件,就可以將非同步操作以同步操作的流程表達出來,避免了層層巢狀的回撥函式。此外,Promise物件提供統一的介面,使得控制非同步操作更加容易。

要理解Promise要知道沒有Promise的回撥地獄

一般我們要在一個函式執行完之後執行另一個函式我們稱之為callback‘回撥’,簡單的寫一下:

setTimeout(function(){
  left(function(){
    setTimeout(function(){
       left(function(){
         setTimeout(function(){
           left();
         },2000);
       });
    }, 2000);
  });
}, 2000);

以上程式碼就是傳說中的回撥地獄,如果有多層業務邏輯巢狀的話,不僅會使程式碼閱讀困難,而且後面維護起來也是難點。
之後在ES6,Promise就應運而生。

Promise語法與then的用法:

var promise = new Promise(function(resolve, reject) {
  // ... some code
  if (/* 非同步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

resolve(value)是在Promise在已經非同步完成成功(Resolved)之後執行的
reject(value)是在Promise在非同步失敗之後(Rejected)執行。
當然,也可以用then來指定:then(resolve,reject)
或者:then(resolve),catch(reject)

promise.then(function(value) {
  // success
}, function(error) {
  // failure
});
//等價於:
promise.then(function(){
  //success
}).catch(function(){
  //failure
})

範例展示:寫一個img圖片載入示例點選

Tips

  1. 連續呼叫回撥:
    以剛開始的回撥地獄為例子:
setTimeout(function(){
  left(function(){
    setTimeout(function(){
       left(function(){
         setTimeout(function(){
           left();
         },2000);
       });
    }, 2000);
  });
}, 2000);
//我們給left函式內容換成console.log(11);
 var p = new Promise((resolve,reject)=>{
    setTimeout( resolve , 2000 )
  })
  .then( ()=>setTimeout( null, 2000 ) )
  .then( ()=>setTimeout(function(){
    console.log(11)
  },2000) )
//這樣在6秒鐘之後會打出11

範例點選
總結:

  • 可以採用連續的then鏈式操作來寫回調(這是因為返回值一直是新的Promise例項)。
  • 以上例子可以看出來只要在第一個promise回撥中新增resolve,之後的連續then就會預設執行。
  1. 可以在then中return出資料,並且這個資料會以引數的形式傳入下一個then。
var p = new Promise(function(resolve,reject){
      var a=1
      resolve(a);  
      
  }).then(function(data){
      console.log(data)
      return ++data;
  }).then( function(data){
      console.log(data)
  } )
//打印出來的結果依次是: 1  2 。

接下來介紹一下catch()

  • catch是用於指定發生錯誤時的回撥函式。(建議不要在then的第二個引數寫rejected狀態,總是使用catch)
  • catch()使回撥報錯時不會卡死js而是會繼續往下執行。點選範例
  • Promise 物件的錯誤具有“冒泡”性質,會一直向後傳遞,直到被捕獲為止。也就是說,錯誤總是會被下一個catch語句捕獲。
    如:
getJSON('/post/1.json').then(function(post) {
  return getJSON(post.commentURL);
}).then(function(comments) {
  // some code
}).catch(function(error) {
  // 處理前面三個Promise產生的錯誤
});

用一段小程式碼來理解catch()

var p = new Promise((resolve,reject)=> {
    n
  } ).then(()=>console.log('執行成功'))
  .catch( ()=>{a;console.log('報錯');} )//這裡我們沒有定義a的值會報錯
  .catch( ()=> console.log('報錯2') )
  .then( ()=>console.log('報錯後的回撥') )
//執行結果是:'報錯2'  '報錯後的回撥'

首先n沒有定義,所以第一層出錯。下一個then的‘執行成功’不會被打出來。而是會被下一個catch捕獲,第一個catch沒有定義a,所以報錯,console.log('報錯')沒辦法打出來,又被下一個catch捕獲: 第二個catch沒有問題:打出‘報錯2’。執行成功傳給下一個then,打出'報錯後的回撥'。

這裡要注意,不管是then或者catch返回的都是一個新的Promise例項!而每個Primise例項都有最原始的Pending(進行中)到Resolve(已完成),或者Pending(進行中)到Reject(已失敗)的過程。

Promise.all()

Promise.all方法用於將多個Promise例項,包裝成一個新的Promise例項。

如:

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

all()接受陣列作為引數。p1,p2,p3都是Promise的例項物件,p要變成Resolved狀態需要p1,p2,p3狀態都是Resolved,如果p1,p2,p3至少有一個狀態是Rejected,p的狀態就變成Rejected(個人感覺很想&&符號連結)

Promise.race();

var p = new Promise( [p1,p2,p3] )

上面程式碼中,只要p1、p2、p3之中有一個例項率先改變狀態,p的狀態就跟著改變。那個率先改變的 Promise 例項的返回值,就傳遞給p的回撥函式。(感覺就是||符號操作~~~)

Promise resolve():

有時需要將現有物件轉為Promise物件,Promise.resolve方法就起到這個作用。
Promise.resolve等價於下面的寫法。

Promise.resolve('foo')
// 等價於
new Promise(resolve => resolve('foo'))

Promise reject()

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

Promise.reject('foo')
// 等價於
new Promise(reject => reject('foo'))

注意,Promise.reject()方法的引數,會原封不動地作為reject的理由,變成後續方法的引數。這一點與Promise.resolve方法不一致。