Promise 對象

Promise 對象用於處理異步請求,保存一個異步操做最終完成(或失敗)的結果數組

 

語法promise

new Promise(
    /* executor */
    function(resolve, reject) {...}
);

/*
   來自谷歌翻譯
      promise:承諾,約定
      resolve:解決,決定,下決心
      reject:拒絕,駁回,抵制 
*/

參數:app

promise 構造函數接受一個 executor 函數做爲參數,該函數的兩個參數分別是 resolve 和 reject,它們是兩個函數(executor 函數 在 Promise 構造函數返回新對象以前被調用)。異步

resolve 函數被調用時,將 promise 對象從 「未完成」 變爲 「成功」 (即 pending --> fulfilled)函數

reject 函數被調用時,將 promise 對象從 「未完成」 變爲 「失敗」 (即 pending --> rejected)fetch

 

描述編碼

promise 對象是一個代理對象(代理一個值),被代理的值在 promise 對象建立時多是未知的。它容許你爲異步操做的成功和失敗分別綁定相應的處理方法,使得異步方法能夠像同步方法那樣返回值,但不是當即返回最終執行結果,而是一個能表明將來出現的結果的 promise 對象。url

上面提到過,一個 promise 對象有三個狀態:spa

• pending:初始狀態,不是成功或失敗狀態prototype

• fulfilled:成功完成

• rejected:失敗

pending 狀態可能觸發 fulfilled 狀態並傳遞一個值給相應的狀態處理方法,也可能觸發 rejected 狀態並傳遞失敗信息。當其中任一種狀況發生時,promise 對象的 then 方法綁定的處理方法就會被調用。

(then 方法包含兩個參數:onfulfilled 和 onrejected,也都是函數。當 promise 對象的狀態爲 fulfilled 時調用,調用 then 的 onfulfilled 方法;反之,調用 onrejected 方法。因此異步操做的完成和綁定處理方法之間不存在競爭)

then 方法的使用語法:

promise.then(function(value) {
  // onfulfilled
}, function(error) {   // 第二個參數是可選的,不必定要提供
  // onrejected 
});

 

示例:

    let myFirstPromise = new Promise(function(resolve, reject){
        // 當異步代碼執行成功時,調用resolve()方法;失敗時,調用reject()方法
        // 此處,使用定時器來模擬異步代碼,實際編碼多是XHR請求或HTML5的一些API方法
        setTimeout(function(){
            //resolve('成功!')  //代碼執行成功,調用resolve()方法
            resolve('成功!')
        }, 2000)
    })
    
    myFirstPromise.then(function(successMessage){
        // successMessage 是上面調用resolve()方法傳入的值
        // successMessage 參數不必定非要是字符串類型,這裏只是舉個例子
        console.log('Yay!'+successMessage)
    })

    console.log('看看個人位置在哪裏?')

運行結果:

瞭解Promise的基本用法之後,經過一個小栗子來了解Promise的應用場景

  // 平常開發中,可能會遇到一個函數的參數依賴於其它函數的返回值,若是其它函數的返回值不能先於執行該函數前返回,就不會獲得咱們預期的結果
  // 此例中,執行add函數時,傳入的getValueX方法直接返回2,而getValueY方法存在1秒鐘的延時,當前拿不到返回值,最終致使求和失敗 
  function add(valueX, valueY) {
    console.log(valueX + valueY)  
  }

  add(getValueX(), getValueY())   // NaN  (valueX -> 2, valueY -> undefined)

  function getValueX() {
    return 2
  }

  // 模仿一個異步請求,1秒後返回數字3
  function getValueY() {
    setTimeout(() => {
      return 3
    }, 1000)
  }
 
經過 Promise對象來實現相似的求和方法
  function add(promiseX, promiseY) {
    return Promise.all([promiseX, promiseY]).then(values => {
      return values[0] + values[1]
    })
  }
  console.log('2秒之後查看x+y求和的結果')
  add(fetchX(), fetchY()).then(sum => {
    console.log('x + y 的和爲:' + sum)      // x + y 的和爲:3
  })

  function fetchX() {
    return new Promise(resolve => {
      resolve(1)
    }).then(res => {
      return res
    })
  }

  function fetchY() {
    return new Promise(resolve => {
      setTimeout(resolve, 2000, 2)
    }).then(res => {
      return res
    })
  }

對比之下,有了Promise對象,就能夠清晰地將異步操做以同步操做的流程表達出來

 

注意:

一、Promise 新建後就會當即執行
  let promise = new Promise((resolve, reject) => {
    console.log('做出承諾:"未來"必定會執行!')
    resolve('resolved')
  })

  promise.then(res => {
    console.log(res)
  })

  console.log('test')

  // 做出承諾:"未來"必定會執行!
  // test
  // resolved
示例中,Promise 新建後當即執行,輸出 "做出承諾:"未來"必定會執行!",當前腳本全部同步任務執行完之後再執行 then 方法
 
二、Promise 的狀態一旦改變,就永久保持該狀態,不會再變了
  let promise  = new Promise((resolve, reject) => {
    resolve('ok')
    throw new Error('error')
  })

  promise.then(res => console.log(res))
         .catch(err => console.log(err))
  // ok

此例中,Promise 在 resolve語句後面再拋出錯誤,因爲此時Promise狀態已經變成 resolved,再拋出錯誤就無效了

 
三、resolve和reject函數的參數會被分別傳遞給回調函數,reject函數的參數一般是Error對象的實例,表示拋出錯誤;resolve函數的參數除了正常值之外,還多是另外一個Promise實例
  let p1 = new Promise(reject => {
    reject(new Error('P1 fail... ...'))
  })
  let p2 = new Promise(resolve => {
    resolve(p1)
  })

  p2.then(res => console.log(res))
    .catch(err => console.log(err))
  
  // Error: P1 fail... ...

 

四、調用 resolve 或 reject 並不會終結 Promise 的參數函數的執行

  new Promise((resolve, reject) => {
    resolve(1)
    console.log(2)
  }).then(res => {
    console.log(res)
  })

  // 2
  // 1
示例中,調用resolve(1)之後,後面的console.log(2)仍是執行了,而且會先執行。resolved 回調函數是在本輪事件循環的末尾執行,老是晚於本輪循環的同步任務
一般,調用 resolve 或 reject之後,Promise的使命就完成了,後續操做應該放在then方法裏面,而不要直接寫在resolve或reject的後面
  // 在resolve或reject前面加上return
  new Promise((resolve, reject) => {
    return resolve(1)
    console.log(2)
  }).then(res => {
    console.log(res)
  })
  // 1

  // 後續操做放在then方法裏
  new Promise((resolve, reject) => {
    resolve(2)
  }).then(res => {
    console.log(res)
    console.log(1)
  })

  // 2
  // 1

經過Promise異步加載圖片的示例:

   function loadImageAsync(url) {
    return new Promise((resolve, reject) => {
      let image = new Image()

      image.onload = function() {
        resolve(url)
      }

      image.onerror = function() {
        reject(new Error(`can't find the image: ${url}`))
      }

      image.src = url
      document.body.appendChild(image)

    })
  }

  loadImageAsync('https://www.baidu.com/img/xinshouye_c9d9de2ff40fa160f807f75f34db4ad0.gif')

 Promise.prototype.then()

它的做用是爲 Promise 實例添加狀態改變時的回調函數,then 方法的第一個參數是 resolved 狀態的回調函數,第二個參數可選,是 rejected 狀態的回調函數

  promise.then(function(res) {
    // success    
  }, function(err) {
    // error
  })

儘管then 方法能夠接受第二個參數,但通常不建議在 then 方法裏定義第二個參數(rejected 狀態的回調函數),建議使用 catch 方法

 

Promise.prototype.catch()

它的做用是指定發生錯誤時的回調函數,至關於 then 方法的第二個參數 promise.then(null, rejection)

catch 方法不只能夠處理異步操做拋出的錯誤,還能夠捕獲then 方法在運行中拋出的錯誤。並且在語義結構上更加接近同步的寫法(try /catch)

{
  // 捕獲 promise 實例狀態從 pending 變爲 rejected 的錯誤
  let promise = new Promise((resolve, reject) => {
    reject(new Error('test error'))
  })

  promise.then(res => console.log(res))
          .catch(err => console.log(err))
  // Error: test error
}

{
  // 捕獲 then 方法中運行時拋出的錯誤
  let promise = new Promise(resolve => {
    resolve(x / 0)
  })

  promise.then(res => console.log(res))
          .catch(err => console.log(err))
  // ReferenceError: x is not defined
}

 

Promise.prototype.finally()

finally 方法用於指定無論 Promise 對象最後狀態如何,都會執行的操做。

finally 方法的回調函數不接受任何參數,所以,也就不知道 Promise 的狀態是 fulfield 仍是 rejected,代表 finally 方法不依賴於 Promise 的執行結果,與狀態無關

  let promise = new Promise((resolve, reject) => {
    if(5 > 3) {
      resolve('計算正確')
    } else{
      reject(new Error('計算出錯了'))
    }
  })

  promise.then(res => console.log(res))
          .catch(err => console.log(err))
          .finally(() => console.log('無論計算對錯,都要執行fianlly'))

  // 計算正確
  // 無論計算對錯,都要執行fianlly

 

Promise.all()

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

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

all 方法接受一個數組做爲參數,參數必須是 Promise 實例

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

(1)、p一、p二、p3 的狀態都變成 fulfilled,p 的狀態纔會變成 fulfilled,此時 p一、p二、p3 的返回值組成一個數組,傳遞給 p 的回調函數

(2)、p一、p二、p3 之中有一個被 rejected,p 的狀態就會變成 rejected,此時第一個被 rejected 的實例的返回值,會傳遞給 p 的回調函數

 

應用場景:要求頁面同時加載3張圖片,只要有一個加載失敗就會報錯

  function loadImgAsync(src) {
    return new Promise((resolve, reject) => {
      let img=document.createElement('img');
      img.src=src;
      img.onload=function(){
        resolve(img);
      }
      img.onerror=function(err){
        reject(err);
      }
    })
  }

  function showImgs(imgs) {
    imgs.forEach(function(img){
      document.body.appendChild(img);
    })
  }

  Promise.all([
    loadImgAsync('http://www.tietuku.com/static/image/icon3.png'),
    loadImgAsync('http://www.tietuku.com/static/image/icon2.png'),
    loadImgAsync('http://www.tietuku.com/static/image/icon1.png')
  ]).then(showImgs)

 

Promise.race()

Promise.race 方法一樣用於將多個 Promise 實例,包裝成一個新的 Promise 實例

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

Promise.all 方法看起來像是 邏輯與 的運算,只有數組中的 Promise 實例對象所有執行成功,新的 Promise 實例對象纔會執行 resolved 回調函數

Promise.race 方法則像是 邏輯或 的運算,就像 race 的中文意思:競賽。只要p一、p二、p3之中有一個實例率先改變狀態,p的狀態就跟着改變。那個率先改變的 Promise 實例的返回值,就傳遞給p的回調函數

將上例需求稍做更改,只要有一個圖片加載成功,便可加載到頁面中,這個時候就要把all 改爲 race 了

  function loadImgAsync(src) {
    return new Promise((resolve, reject) => {
      let img=document.createElement('img');
      img.src=src;
      img.onload=function(){
        resolve(img);
      }
      img.onerror=function(err){
        reject(err);
      }
    })
  }

  function showImgs(img){
    let p=document.createElement('p');
    p.appendChild(img);
    document.body.appendChild(p)
  }

  Promise.race([
    loadImgAsync('http://www.tietuku.com/static/image/icon3.png'),
    loadImgAsync('http://www.tietuku.com/static/image/icon2.png'),
    loadImgAsync('http://www.tietuku.com/static/image/icon1.png')
  ]).then(showImgs)

相關文章
相關標籤/搜索