Promise原理以及實現(Promise/A+規範) 上

什麼是Promise?

Promise是ES6提供的一種異步解決方案, 它容許將寫法複雜的傳統回調函數和監聽事件的異步操做, 用同步代碼的形式將結果傳達出來. 從本意講, promise表示承諾, 就是承諾必定時間處理異步(也可同步)以後會給你一個結果, 而且這個結果會根據狀況有着不一樣的狀態.javascript

傳統回調的弊端

例子:java

function fn1(value, callback) {
  setTimeout(() => {
    let a = 1
    for(let i = 1; i <= value; i++) {
      a *= i
      callback(a)
    }
  }, )
}

fn1(10000000, function(result) => {
  console.log(result)
})
複製代碼

上面代碼是先進行階乘運算, 計算完後執行callback回調. 假如爲了獲得10000000的的階乘, 由於運算量大, 不讓他阻塞js, 使其在異步中執行, 所以須要同回調來獲得結果.數組

可是此時又須要將這個結果在fn2函數中作一些減法處理:promise

function fn2(value, callback) {
  setTimeout(() => {
    const b = value - 1 - 2 - 3 - 4 - 1000
    callback(b)
  }, 100)
}

fn1(10000000, function(result1) => {
  fn2(result1, function(result2) => {
    console.log(result)
  })
})
複製代碼

而後須要在fn3函數中作一些乘法處理, 在fn4函數中作一些加法處理...fn5 fn6 fn7...fn100........bash

function fn3(value, callback) {
  // value = x*x*123...
  callback(value)
}

function fn4(value, callback) {
  // value = x+x+123...
  callback(value)
}

fn1(10000000, function(result1) => {
  fn2(result1, function(result2) => {
    fn3(result2, function(result3) => {
      fn4(result3, function(result4) => {
        ...
        fn100(result99, function(result100) => {
          console.log(result)
        })
      })
    })
  })
})
複製代碼

經過一百次回調獲得最終結果...就成了回調地獄, 真實狀況雖然沒有這麼多層級, 可是每個處理函數的內容也不可能這麼簡單, 到時候確定很臃腫不美觀, 說不定其中的某個回調的結果在哪裏都找不到(result1, result2, result3...)...併發

Promise的出現

Promise表示承諾, 它也會像回調函數同樣, 承諾一個結果, 可是這個結果會根據promise的狀態經過不一樣的方式(成功或者失敗)傳遞出來.異步

回調的弊端:async

  • 層層嵌套, 代碼書寫邏輯與傳統順序有差別, 不利於閱讀與維護
  • 其中異步操做順序變動時, 可能須要大量從新變更

Promise能解決兩個問題:函數

  • 回調地獄, 它能經過函數鏈式調用方式來執行異步而後處理結果, 使得代碼邏輯書寫起來像同步ui

  • 支持併發, 好比說獲取多個併發請求的結果

Promise的狀態

promise狀態圖解

Promise是一個構造函數, 經過new Promise的方式獲得一個實例對象, 建立時接受一個執行函數(下面簡稱 fn )做爲參數, 這個執行函數也同時也有兩個形參resolve, reject

const promise = new Promise((resolve, reject) => {
  // ...do something asynchronous
  // resolve sth or reject sth
})
複製代碼

promise自己有三種狀態:

  • pending -> 等待
  • fulfilled -> 執行態
  • rejected -> 拒絕態

Promise是一個構造函數, 他須要經過 new 實例化來使用, 實例化的同時 Promise內部的狀態爲初始的 pending

fn 表示傳入一個回調函數, 它會被Promise內部傳入兩個參數 resolve, reject

當fn內部的代碼執行resolve 或者 reject時, 它的內部的狀態都會變化

與 resolve reject 對應的狀態爲

resolve -> fulfilled
reject  -> rejected
複製代碼

一旦promise被resolve或者reject, 不能再遷移到其餘任何狀態

基本過程:

  1. 初始化Promise狀態(pending)
  2. 執行 then(..) 註冊回調處理數組(then 方法可被同一個 promise 調用屢次)
  3. 當即執行 Promise 中傳入的 fn 函數, 將Promise 內部 resolve、reject 函數做爲參數傳遞給 fn , 按事件機制時機處理
  4. Promise中要保證, then方法傳入的參數 fulfilled 和 rejected, 必須在then方法被調用的那一輪事件循環以後的新執行棧中執行

上述能夠先不用管, 先來看看用法

Promise對象的方法

1. then方法註冊當resolve(成功)/reject(失敗)時的回調函數

promise.then(fulfilled, rejected)
// fulfilled 是promise執行當resolve 成功時的回調
// rejected 是promise執行當reject 失敗時的回調
複製代碼

2. resolve(成功)fulfilled會被調用

當 fn 內部執行 resolve 方法時(此時的Promise內部狀態爲 resolved), 能夠傳入一個參數 value, 便會執行 then 中的 fulfilled 回調, 並把 value 做爲參數值傳入,

const promise = new Promise((resolve, reject) => {
  let value = 1 + 1
  resolve(value)
}).then(function(res) {
  console.log(res) // --> 2 , 這裏的 res 就是resolve傳入的 value
})
複製代碼

3. reject(失敗)rejected會被調用

同理 reject 也同樣 不一樣之處在於, 須要在then中多傳入一個執行回調:

const promise = new Promise((resolve, reject) => {
  let value = 1 + 1
  reject(value)
})
.then(function(res) {
  console.log(res) // fulfilled 不會被調用
}, function(err) {
  console.log(err) // 2
})
複製代碼

上面代碼中, fn 代碼 執行了 reject, 此時的Promise內部狀態爲 rejected. 所以就會只執行then中的第二個執行回調(rejected), 一樣 reject 接受一個 value 參數 傳給 下一個then 中的 rejected 執行後續錯誤的回調

4. promise.catch捕獲錯誤

鏈式調用寫法中能夠捕獲前面的 then 中 resolved 回調發生的異常

promise.catch(rejected)
// 至關於
promise.then(null, rejected)
// 注意: rejected 不能捕獲當前 同一層級fulfilled 中的異常

promise.then(fulfilled, rejected) 
// 能夠寫成:
promise.then(fulfilled)
       .catch(rejected)   
複製代碼

當 fullfill中發生錯誤時:

const promise = new Promise((res, rej) => {
  res(1)
})
.then(res => {
  let a = 1
  return res + a.b.c
})
.then(res => {
  return res + 1
}, err => {
  // 會捕獲上一級的錯誤
  console.log(err) // Cannot read property 'c' of undefined
})

複製代碼
const promise = new Promise((res, rej) => {
  res(1)
}).then(res => {
  let a = 1
  return res + a.b.c
}).then(res => {
  return res + 1
}).then(res=> {
  console.log(res)
}).catch(err => {
  // 上面任何一級發生錯誤 最終都會轉入catch
  console.log(err) // TypeError: Cannot read property 'c' of undefined
})
 
複製代碼

上述故意讓代碼發生錯誤(a.b.c), 就會轉到catch函數, 中間不管有多少個 then 都不會執行, 除非 then 中傳入了失敗的回調:

const promise = new Promise((res, rej) => {
  res(1)
})
  .then(res => {
    let a = 1
    return res + a.b.c
  })
  .then(res => {
    return res + 1
  }, err => { // 這裏會被先捕獲
    console.log(err) // TypeError: Cannot read property 'c' of undefined
  })
  .then(res=> {
    console.log(res)
  }).catch(err => {
  // 已經被上面的 rejected 回調捕獲 就不會執行 catch 了
  console.log(err) 
  })
複製代碼

5. promise鏈式寫法

promise.then方法每次調用 都返回一個新的promise對象 因此能夠鏈式寫法
複製代碼
function taskA() {
    console.log("Task A")
}
function taskB() {
    console.log("Task B")
}
function rejected(error) {
    console.log("Catch Error: A or B", error)
}
var promise = Promise.resolve()
promise
  .then(taskA)
  .then(taskB)
  .catch(rejected) // 捕獲前面then方法中的異常
複製代碼

須要注意的是 當then執行 rejected 回調後 傳入的 參數會被 下一級 的 resolved 執行回調接受 而不是 rejected:

const promise = new Promise((res, rej) => {
  res(1)
})
  .then(res => {
    let a = 1
    return res + a.b.c
  })
  .then(res => {
    return res + 1
  }, err => {
    console.log(err) // 這裏捕獲到錯誤後, 返回的值 仍然是會被 下一級 then 中的 resolved 執行回調 接收
    return 'dz'
  })
  .then(res=> {
    console.log(res) // dz
  }, err => {
    console.log(err)
  })
複製代碼

Promise的靜態方法

1. Promise.resolve返回一個fullfill狀態的promise對象

Promise.resolve('dz').then(res => { console.log(res) })// --> dz
// 至關於
new Promise((resolve) => {resolve('dz')}).then(res => { console.log(res) }) // --> dz
複製代碼

2. Promise.reject 同理返回一個rejected狀態的promise對象

3. Promise.all接受一個數組, 數組每一個元素裏面爲promise對象, 返回一個由每個promise對象執行後 reject 狀態組成的數組

只有每一個promise對象狀態`都爲resolve`纔會調用, 一般用來處理多個並行異步操做

複製代碼
const p1 = Promise.resolve('dz1')
const p2 = Promise.resolve('dz2')
const p3 = Promise.resolve('dz3')
Promise.all([p1, p2 , p3]).then(res => {
  console.log(res) // --> ['dz1', 'dz2', 'dz3'], 結果與數組中的promise返回的結果順序一致
})
複製代碼

4. Promise.race 一樣接受一個數組, 元素爲promise對象

Promise.race只要其中某個元素先進入 fulfilled 或者 rejected 狀態就會進行後面的處理(執行then)
複製代碼
function timer(delay) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(delay)
    }, delay)
  })
}
Promise.race([
  timer(10),
  timer(20),
  timer(30),
]).then(res => { console.log(res) }) // -> 10
複製代碼

5. finally 不管 Promise 狀態如何 都會執行

不管Promise返回的結果是什麼都會執行 finally 而且 不會改變 Promise 的狀態

Promise.resolve(1).finally(()=>{})
Promise.reject(1) .finally(()=>{})
複製代碼
相關文章
相關標籤/搜索