promise的理解和使用

BAHG發表於2020-05-18

1. Promise是什麼

1.1 promise 的理解

1. 抽象表達:

  Promise 是 JS 中進行非同步程式設計的新的解決方案(舊的是純回撥形式)

2. 具體表達:

 (1)從語法上說:Promise 是一個建構函式

 (2)從功能上說:promise 物件用來封裝一個非同步操作並可以獲取其結果

1.2 promise 的狀態和狀態改變

三種狀態:

  1. pending: 初始狀態,既不是成功,也不是失敗狀態。

  2. fulfilled: 意味著操作成功完成。

  3. rejected: 意味著操作失敗。

pending:等待狀態,比如正在進行網路請求,或者定時器沒有到時間。

fulfill:滿足狀態,當我們主動回撥了resolve時,就處於該狀態,並且會回撥.then()

reject:拒絕狀態,當我們主動回撥了reject時,就處於該狀態,並且會回撥.then()或者.catch()

兩種狀態改變:

  1. pending 變為 fulfilled

  2. pending 變為 rejected

當我們 new Promise 的時候,此時的 Promise物件是 pending 狀態,它可能會變為 fulfilled 狀態並傳遞一個值給相應的狀態處理方法,也可能變為 rejected 狀態並傳遞失敗資訊。當其中任一種情況出現時,Promise 物件的 then 方法繫結的處理方法(handlers )就會被呼叫(then方法包含兩個引數:onfulfilled 和 onrejected,它們都是 Function 型別。當 Promise 狀態為 fulfilled 時,呼叫 then 的 onfulfilled 方法,當 Promise 狀態為 rejected 時,呼叫 then 的 onrejected 方法, 所以在非同步操作的完成和繫結處理方法之間不存在競爭)。

因為 Promise.prototype.then 和 Promise.prototype.catch 方法返回的是 promise 物件, 所以它們可以被鏈式呼叫。

1.3 promise 的基本流程

 1.4 promise 的基本使用

//1.建立一個新的promise物件
const p = new Promise((resolve, reject) => { //執行器函式是同步回撥!
  console.log('執行 executor') //立刻執行
  //2.執行非同步操作
  setTimeout(() => {  
    const time = Date.now()
    //3.1 成功,呼叫resolve(value)
    if( time % 2 === 0 ){
      resolve('成功的資料,time=' + time)
    } else {
    //3.2 失敗,呼叫reject(reason)
      reject('失敗的資料,time=' + time)
    }
  }, 1000)
})
console.log('new Promise()之後')  //先輸出 '執行 exceutor'

p.then(
  value => { // onfulfilled函式,自動接收成功的value
    console.log('成功的回撥', value)
  },
  reason => { // onrejected函式,自動接收失敗的reason
    console.log('失敗的回撥', reason)
  }
)

2. 為什麼要使用 Promise

 (1) 指定回撥方式更加靈活

     舊的:必須在啟動非同步任務前指定
     promise:啟動非同步任務——返回promise物件——給promise物件繫結回撥函式(甚至可以在非同步函式結束之後指定)

 (2)支援鏈式呼叫, 解決回撥地獄 


  function successCallback(result) {   

    console.log('聲音檔案建立成功' + result)

   }

function failureCallback(error) {
   console.log('聲音檔案建立失敗' + error)
 }

 /* 1.1 純回撥函式 */
 //啟動任務(audioSettings)前必須指定回撥函式(callback)
 createAudioFileAsync(audioSettings, successCallback, failureCallback)

 /* 1.2 promise */
 //可在啟動任務(audioSettings)後指定回撥函式(callback)
 const promise = createAudioFileAsync(audioSettings)
 setTimeout(() => {
   promise.then(successCallback, failureCallback)
 }, 1000)



 /* 2.1 回撥地獄 */
 //回撥函式的巢狀
 doSomething(function (result) { //第一個函式function就是sucessCallback
   doSomethingElse(result, function (newResult) {
     doThirdThing(newResult, function (finalResult) {
       console.log('Got the final result' + finalResult)
     }, failureCallback)
   }, failureCallback)
 }, failureCallback)


 /* 2.2 鏈式呼叫 */
 doSomething().then(function (result) { //result是doSomething函式成功執行的返回值
     return doSomethingElse(result) //執行器函式,同步回撥
   })
   .then(function (newResult) { //newResult是doSomethingElse成功執行的返回值
     return doThirdThing(newResult)
   })
   .then(function (finalResult) {
     console.log('Got the final result' + finalResult)
   })
   .catch(failureCallback) //統一的錯誤處理


 /* 2.3 async/await : 回撥地獄的終極解決方案 */
 //相比於promise 去掉了回撥函式
 async function request() {
   try {
     const result = await doSomething()
     const newResult = await doSomethingElse(result)
     const finalResult = await doThirdThing(newResult)
     console.log('Got the final result' + finalResult)
   } catch (error) {
     failureCallback(error)
   }
 }

3. 如何使用 Promise

3.1 語法(API)

(1)基本語法

new Promise( function(resolve, reject) {...} /* executor */  );
executor是帶有 resolve 和 reject 兩個引數的函式 。Promise建構函式執行時立即呼叫 executor 函式, resolve 和 reject 兩個函式作為引數傳遞給executor(executor 函式在Promise建構函式返回所建promise例項物件前被呼叫)。resolve 和 reject 函式被呼叫時,分別將promise的狀態改為 fulfilled(完成)或 rejected(失敗)。executor 內部通常會執行一些非同步操作,一旦非同步操作執行完畢(可能成功/失敗),要麼呼叫resolve函式來將promise狀態改成fulfilled,要麼呼叫reject 函式將promise的狀態改為rejected。如果在executor函式中丟擲一個錯誤,那麼該promise 狀態為rejected。executor函式的返回值被忽略。

 (2)方法

 (3)Promise 原型

 (4)程式碼展示

new Promise( (resolve, reject) => {
  setTimeout( () => {
    resolve('成功') //resolve就像是一個傳遞資料的運輸機
  }, 1000 )
})
.then(
  value => {
    console.log('onResolved()1', value)
  }
)
.catch(
  reason => {
    console.log('onRejected()1', reason)
  }
)

const p1 = new Promise((resolve, reject) => {
  resolve(1)
})
const p2 = Promise.resolve(2)
const p3 = Promise.reject(3)
// p1.then( value => {console.log(value)} )
// p2.then( value => {console.log(value)} )
// p3.catch( reason => {console.log(reason)} )

//const pAll = Promise.all([p1,p2,p3])
const pAll = Promise.all([p1,p2])
pAll.then(
  values => {
    console.log('all onResolved()', values)
  },
  reason => {
    console.log('all onRejected()', reason)
  }
)

const pRace = Promise.race([p1,p2,p3])
pRace.then(
  value => {
    console.log('race onResolved()', value)
  },
  reason => {
    console.log('race onResolved()', reason)
  }
)

有關更多 promise 的原理和 then,catch,all,race的用法請參考 https://blog.csdn.net/qq_34645412/article/details/81170576https://segmentfault.com/a/1190000007463101#articleHeader2https://www.jianshu.com/p/001d22a44f85

3.2 Promise 的幾個關鍵問題

1. 如何改變promise的狀態?

    (1) resolve(value): 如果當前是pending就會變為fulfilled

    (2) reject(reason): 如果當前是pending就會變為rejected

    (3) 丟擲異常: 如果當前是pending就會變為rejected

2. 一個promise指定多個成功/失敗回撥函式, 都會呼叫嗎?

    當promise改變為對應狀態時都會呼叫
 const p = new Promise((resolve, reject) => {
      //resolve('Promise狀態會被標記為fulfilled')
      // reject('Promise狀態會被標記為rejected')
      throw new Error('Promise狀態會被標記為rejected')
    });

 p.then(
   value => {
     console.log('value1', value) 
   },
   reason => {
     console.log('reason1', reason) // reason1 Error: Promise狀態會被標記為rejected
   }
 )
p.then( value
=> { console.log('value2', value) }, reason => { console.log('reason2', reason) // reason2 Error: Promise狀態會被標記為rejected } )

3. 改變promise狀態和指定回撥函式誰先誰後?

    (1) 都有可能, 正常情況下是先指定回撥再改變狀態, 但也可以先改狀態再指定回撥
    (2) 如何先改狀態再指定回撥?
      ①在執行器中直接呼叫resolve()/reject()
      ②延遲更長時間才呼叫then()
    (3) 什麼時候才能得到資料?
      ①如果先指定的回撥, 那當狀態發生改變時, 回撥函式就會呼叫, 得到資料
      ②如果先改變的狀態, 那當指定回撥時, 回撥函式就會呼叫, 得到資料
new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve(1) //後改變狀態(同時指定資料),非同步執行回撥函式
      }, 1000)
    }).then( //先指定回撥函式,儲存當前指定的回撥函式
      value => {},
      reason => {
        console.log('reason', reason)
      }
    )

    new Promise((resolve, reject) => {
      resolve(1) //先改變狀態(同時指定資料)
    }).then( //後指定回撥函式,非同步執行回撥函式
      value => {
        console.log('value', value)
      },
      reason => {
        console.log('reason', reason)
      }
    )
    console.log('-----') //先輸出----, 再輸出value 1,因為回撥函式是非同步執行,會被放入到佇列中待執行

 4. promise.then()返回的新promise的結果狀態由什麼決定?

    (1) 簡單表達: 由then()指定的回撥函式執行的結果決定
    (2) 詳細表達:
      ① 如果丟擲異常,則新promise變為rejected, reason為丟擲的異常,丟擲什麼,reason就是什麼
      ② 如果返回的是非promise的任意值,則新promise變為fulfilled, value為返回的值,當沒有return語句的時候value為undefined
      ③ 如果返回的是另一個新promise, 此promise的結果就會成為新promise的結果
new Promise((resolve, reject) => {
      // resolve(1)
      reject(1)
    }).then(
      value => {
        console.log('onResolved1()', value)
        // return 2
        // return Promise.resolve(3)
        // return Promise.reject(4)
        throw 5
      },
      reason => {
        console.log('onRejected1()', reason)
        // return 2
        // return Promise.resolve(3)
        // return Promise.reject(4)
        throw 5
      }
    ).then(
      value => {
        console.log('onResolved2()', value)
      },
      reason => {
        console.log('onRejected2()', reason)
      }
    )

5. promise 如何串聯多個操作任務?

new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('aaa')
      }, 1000)
    }).then(res => { // 由於只傳遞了一個引數res 所以可以省略括號
      console.log(res, '第一次處理');
      // res = res + '111' 這樣比較混亂 
      return new Promise((resolve) => { // 當我們只需要用resolve的時候可以省略reject
        resolve(res + '111') // 我們這個時候不需要網路請求 而是自己處理
      }).then(res => {
        console.log(res, '第二次處理');
        return new Promise((resolve, reject) => {
          // resolve(res + '222')
          reject('error message')
        }).then(res => {
          console.log(res, '第三次處理');
        }).catch(err => {
          console.log(err)         
        })
      })
    })


// 由於第二次和第三次處理都沒有使用非同步處理(setTimeout) 所以我們可以簡寫 return new Promise((resolve, reject) => { setTimeout(() => { resolve('aaa') }, 1000) }).then(res => { console.log(res, '第一次處理'); return Promise.resolve(res + '111') }).then(res => { console.log(res, '第二次處理'); // return Promise.resolve(res + '222') // return Promise.reject(res + '222') throw 'error message' }).then(res => { console.log(res, '第三次處理'); }).catch(res => { console.log('error message'); })

// 再次簡寫 直接 return res + '111' 省略 Promise.resolve new Promise((resolve, reject) => { setTimeout(() => { resolve('aaa') }, 1000) }).then(res => { console.log(res, '第一次處理'); return res + '111' }).then(res => { console.log(res, '第二次處理'); return res + '222' }).then(res => { console.log(res, '第三次處理'); })

 6. promise異常傳透?

    (1) 當使用 promise 的 then 鏈式呼叫時, 可以在最後指定失敗的回撥,
    (2) 前面任何操作出了異常, 都會傳到最後失敗的回撥中處理
new Promise((resolve, reject) => {
      //resolve(1)
      reject(1)
    }).then(
      value => {
        console.log('onResolved1()', value)
        return 2
      },
      // 內部預設會將錯誤逐級傳遞直至最後的catch 
      // reason => Promise.reject(reason)
      // reason => {
      //   throw reason
      // }         
    ).then(
      value => {
        console.log('onResolved2()', value)
        return 3
      }
    ).then(
      value => {
        console.log('onResolved3()', value)
      }
    ).catch(reason => {
      console.log('onRejected1()', reason)
    })

 7. 中斷promise鏈?

    (1) 當使用promise的then鏈式呼叫時, 在中間中斷, 不再呼叫後面的回撥函式
    (2) 辦法: 在回撥函式中返回一個pending狀態的promise物件
new Promise((resolve, reject) => {
      reject(1)
    }).then(
      value => {
        console.log('onResolved1()', value)
        return 2
      }
    ).then(
      value => {
        console.log('onResolved2()', value)
        return 3
      }
    ).then(
      value => {
        console.log('onResolved3()', value)
      }
    ).catch(reason => {
      console.log('onRejected1()', reason)
      return new Promise(() => {}) //返回一個pending的promise 中斷promise鏈,後面程式碼不會再執行
    }).then(
      value => {
        console.log('onResolved4()', value)
      },
      reason => {
        console.log('onRejected4()', reason)
      }
    )

相關文章