精讀Javascript系列(9coll) Promise — 回調地獄、Promise構造器

前言:當前進階進度

看一下進階時間線:
在這裏插入圖片描述看到夢想之前,我只想到了無限長的距離……
首先解釋一下這張時間線,它的起點就是入門完成,所以自然會從印象最深的概念開始入手——即異步這裏。所以較複雜的知識已經在之前搞完了,現在就是比較簡單的了。

本文將簡單的介紹一下Promise以及promisify的方法,不涉及底層源碼細則,最多從**Promise A+**規範角度進行解讀。

Yoshi, 開始吧。

回調地獄

在認識Promise之前,有必要知道什麼是 回調地獄(Callback Hell),它還有一個顯得很霸氣卻極爲令所有JS開發者感到無比厭煩的名字—— 厄運金字塔(Pyramid of doom)。之所以先了解這些,這是因爲Promise本來就是爲了避免在多重異步操作時產生深層次的嵌套回調提出的一種新型異步解決方案。

老樣子,來個栗子

  function load(src,cb){
      let es = document.createElement('script')
      es.src = src 
      document.body.append(es)
      es.onload=()=>{cb()}
      es.onerror=()=>{cb(new Error('failed to load '+src ))  }
  }
  load('./t1.js', err=>{
      if(err===undefined) {
          f1();      // t1內部的函數
      } else {
          console.log(err.message);
      }
  })

上面的代碼是很很常見的需求,隨便添加一個js,然後執行裏面的內容。
但是也會需要加載多個腳本,同時執行一些函數:

 load('./t1.js', err=>{
     if(err===undefined) {
         f1();      // t1內部的函數
         load('./t2.js', err=>{
             if(err===undefined) {
                 f2();      // t2內部的函數
                 load('./t3.js', err=>{
                     if(err===undefined) {
                         f3();      // t3內部的函數
                     } else {
                         console.log(err.message);
                     }
                 })
             } else {
                 console.log(err.message);
             }
         })
     } else {
         console.log(err.message);
     }
 })

這樣的多重操作,會隨着嵌套層數變深逐漸失控,於是便形成了臭名昭著的厄運金字塔。爲了解決這個問題,於是Promise便應運而生——主要是將深層回調變成了鏈式調用,這樣能更符合人類邏輯:

第一步:先讓load返回一個Promise

  // 每個 load 返回一個 Promise 對象
  // 每個Promise對象都有一個 then 方法。
  function load(src){
      let es = document.createElement('script')
      es.src = src 
      document.body.append(es)
      return new Promise((resolve, reject)=>{
          es.onload=()=>{resolve('success '+ src)}
          es.onerror=()=>{reject(new Error('failed to load '+src ))  }
      })
  }

第二步: 鏈式調用:

 load('./t1.js')
     .then(str=>{
         console.log(str);
         f1();
         return load('./t2.js')   //這裏返回一個Promise
     })                           // 直接就能用 then
     .then(str=>{
         console.log(str);
         f2();
         return load('./t3.js')		// 再返回一個Promise
     })
     .then(str=>{           
         console.log(str);
         f3();
     })
     .catch(er=>{
         console.log(er.message);
     })

是不是有一種……之後的舒爽感??

Promise極其重要的特徵是鏈式調用,每個load函數返回一個Promise,該Promise確定是異步加載成功還是失敗之後就會被返回然後提供給下一個操作使用。 一個Promise隊列就像是使用同步代碼來實現異步操作一樣。


認識Promise

這裏就引用 MDN 上的Promise概念:

Promise 對象是一個代理對象(代理一個值),被代理的值在Promise對象創建時可能是未知的。它允許你爲異步操作的成功和失敗分別綁定相應的處理方法。 這讓異步方法可以像同步方法那樣返回值,但並不是立即返回最終執行結果,而是一個能代表未來出現的結果的promise對象

重要說明

  • Javascript中的Promise也參考了Promise/A+規範。
  • Promise中的概念還是 ** MDN** 比較全面,因此我會根據 MDNPromise/A+ 的內容綜合說明。
  • 嘛,其實Promise概念不如何重要,重要的是如何應用它

正文開始。

Promise構造:

語法:

var p promise = new Promise(function(resolve, reject){
// executor
})

參數:

  • executor : 在新建一個Promise實例時,必須向 Promise構造函數 傳遞一個回調函數(名曰:executor),在新建對象時會立即執行它。
    返回值:
  • 一個被設置了狀態的Promise,它可能會有valuereason。 亦或是該Promisepending

附註 I:關於executorresolvereject

  1. 它必須有兩個回調函數:resolvereject,無論是resolve還是 reject,這兩個都是Promise內部提供的。
  2. 首次執行resolve時,Promise狀態就已經被固定fulfilled ,並且會有一個固定value
  3. 首次執行reject時,Promise狀態就被固定rejected ; 並且會有一個固定reason
  4. 一旦Promise確定, 之後不論是再執行resolvereject亦或是拋出異常,都將被忽略。換言之,當Promise被確定爲fulfilled後,就無法再轉換到rejected,反之亦然。
  • 既沒有執行resolve也沒有reject或拋出異常,那麼Promise狀態爲pending,此時它可能會被轉換到fulfilledrejected

附註 II: 關於settled

  • 一旦Promise被確定爲fulfilledrejected, 都可以說Promisesettled
  • 一個未被settledPromise將處於pending狀態。

注意
現在最好是使用Firefox瀏覽器進行實驗,因爲Chromeconsole做了優化,結果可能和理論不一致

綜上所述,可以知道:

(一) Promise狀態一經確定(settled),就不會再二次改變:
  let pr = new Promise((res,rej)=>{
      res('im fulfilled')
      console.log(11111);
      
      res('im fullfiled two')
      console.log(22222);
      
      rej(new Error('im rejected'))
      throw new Error('hei, im exception')
  })
  console.log('33333');
  
  console.log(pr);
  

瀏覽器輸出:
11111務必注意:

  1. 111112222233333是立即輸出的,因爲executor是立即執行的。
  2. 因爲executor沒有異步操作, 所以Promise會立即確定狀態。
  3. 一個executor可以多次調用resolvereject,只是第一次調用纔會生效,其他全被忽略
  let pr = new Promise((res,rej)=>{
      rej('im rejected')
      console.log(11111);
      res('im fullfiled two')	// 被忽略!!
      console.log(22222);		// 正常執行
   })
   pr.then(undefined,er=>{})    // 如果註釋掉,它會拋出一個異常!!!
  console.log('33333');
  
  console.log(pr);
  

瀏覽器輸出:
22222

(二) Promise如果是異步確定狀態的,那麼在此之前處於pending
 let pr = new Promise((res,rej)=>{
     console.log(11111);
     setTimeout(res,1000,'ha,ha,ha,ha');
     console.log(22222);
     setTimeout(res,500, 'en,en,en,en');  // 先執行,
 })
 console.log(3333);
 console.log(pr);		// pending
 setTimeout(console.log, 1500, pr)  // fulfilled

瀏覽器輸出:
33333
根據以上推論,如果Promise不執行resolve時,那麼就永遠無法被settled,即會一直處於pending狀態。

(三) Promise是無法手動取消的

一個promise返回後由GC回收,並且在JS中沒有提供關於對象的銷燬方法,因此它會一直存在當當前宏任務結束。


最後

這是關於Promise的核心部分,爲了防止知識點太分散,決定一點點的記錄筆記。要不然總是知識點一旦堆積起來就變得複雜難讀,反省了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章