Promise原理與實現探究的一種思路

来源:http://www.cnblogs.com/zqzjs/archive/2017/12/10/8017497.html
-Advertisement-
Play Games

寫在前面 這個文章,展現的是一個實現Promise的思路,以及如何發現和處理問題的情境。 從現有的Promise分析 如果我們想要自己實現一個簡單的 ,那現有規範規定的 肯定是我們最好的參照。 我們先看下 怎麼使用: 來看下返回的 是什麼,以及它的結構是怎麼樣的: 再進行一些具體操作 從Promis ...


寫在前面


這個文章,展現的是一個實現Promise的思路,以及如何發現和處理問題的情境。

從現有的Promise分析


如果我們想要自己實現一個簡單的Promise,那現有規範規定的Promise肯定是我們最好的參照。

我們先看下Promise怎麼使用:

var promise1 = new Promise(function(resolve, reject){
      // 成功後的TODO
      resolve(value);
      // 失敗後的TODO
      reject(err);
})

來看下返回的promise1是什麼,以及它的結構是怎麼樣的:

image

再進行一些具體操作

var promise1 = new Promise(function(resolve, reject) {
    resolve('zqz')
})

promise1.then(function(result) {
   console.log(result) 
}).catch(function(err){
   console.log(err) 
})

// => 'zqz'

11

var promise1 = new Promise(function(resolve, reject) {
    reject('出現異常')
})
promise1.then(function(result) {
   console.log(result) 
}).catch(function(err){
   console.log(err) 
})

// => '出現異常'

22

從Promise的 使用方式上 和 實例 可以看到哪些東西:

  • Promise是一個構造函數
  • Promise包含一個參數,這個參數類型是一個_匿名函數_
  • 匿名函數包括2個形參,分別是 rejectresolve
  • 這兩個形參類型是 函數 ,且 rejectresolve 都有一個參數, 參數類型不限定
  • 實例 是個 Promise
  • 實例的 原型 上掛載了 2個方法,分別是 thencatch,同時then可以有多個,所以需要一個回掉函數隊列
  • 實例上 有2個屬性,分別是 PromiseStatusPromiseValue
  • Promise根據定義 PromiseStatus 需要有 3種狀態,分別是 pending, fulfilledrejected

根據上面的分析情況,我們先簡單的來構造一個雛形。

function Promise(fn) {
  this.PromiseStatus = 'pending';
  this.PromiseValue = '';

  this.resolvedCb = [];
  this.rejectedCb = [];

  var self = this;

  var resolve = function (result) {
    // 判斷狀態
     if (self.PromiseStatus === 'pending') {
      self.PromiseStatus = 'resolved';
      self.PromiseValue = result;
       // resolvedCb 隊列依次執行
       for (var i = 0;i < self.resolvedCb.length; i++) {
         self.resolvedCb[i](result)
       }
     }
   }
 
   var reject = function (err) {
     // 判斷狀態
     if (self.PromiseStatus === 'pending') {
        self.PromiseStatus = 'rejected';
        self.PromiseValue = err;
       
        // rejectedCb 隊列依次執行
       for (var i = 0;i < self.rejectedCb.length; i++) {
         self.rejectedCb[i](result)
       }
     }
   }
 
 // 錯誤處理 -> rejected
  try {
    fn(resolve, reject)
  } catch(e) {
    reject(e)
  }
  
}

當然這還不夠,因為重要的兩個功能thencatch還沒有實現。

從現有的 then 分析


分析下then的使用

promise1.then(function(value){
   // todo
   return value;
})
.then(function(value1){
    // todo
    return value1;
})
.then(function(value2){
  // todo
  return value2;
})
  • promise1 返回的值 需要塞到第一個then中函數的value上
  • 鏈式調用,多次調用
  • then返回的是一個新的Promise
  • then可以接受2個函數作為參數,一個是成功函數,一個是失敗函數
  • return 的值 直接作為下個 then 中匿名函數的入參

根據Promise返回的實例,我們可看出來then是掛載在 Promise原型鏈上。

我們先實現一個大體的框架:

Promise.prototype.then = function (handleSuccess, handleFail) {
    var self = this;
    var PromiseStatus = this.PromiseStatus;

    if(typeof handleSuccess === 'function') {
      handleSuccess = handleSuccess;
    } else {
      handleSuccess = function (result) {}
    }

    if(typeof handleFail === 'function') {
      handleFail = handleFail;
    } else {
      handleFail = function (err) {}
    }

    if(PromiseStatus === 'pending') {
        return new Promise(function(resolve, reject) {
          self.resolvedCb.push(handleSuccess);
          self.rejectedCb.push(handleFail);
        })
    }

    if(PromiseStatus === 'resolved') {
        return new Promise(function(resolve, reject) {
          var result = handleSuccess(self.PromiseValue);
          resolve(result);
        })
    }

    if(PromiseStatus === 'rejected') {
      return new Promise(function(resolve, reject) {
        var result = handleFail(self.PromiseValue);
        reject(result);
      })
    }
    
}

我們先用一下,看下是否符合期望

方式一(無非同步操作):

function promise1() {
  return new Promise(function(resolve, reject){
      console.log('執行promise1')
      resolve('zqz');
  })
}

promise1().then(function(result){
  console.log('執行1', 'result:'+result)
  return result + '11';
})
.then(function(result){
    console.log('執行2', 'result:'+result)
  return result + '22';
})
// => 執行promise1
// => 執行1 result:zqz
// => 執行2 result:zqz11
// => Promise {PromiseStatus: "resolved", PromiseValue: "zqz1122", resolvedCb: Array(0), rejectedCb: Array(0)}

這樣使用沒有問題!

方式二(有非同步操作):

function promise1() {
  return new Promise(function(resolve, reject){
    // 非同步操作
    setTimeout(function(){
      console.log('執行promise1')
      resolve('zqz');
    },1000)

  })
}

promise1().then(function(result){
  console.log('執行1', 'result:'+result)
  return result + '11';
})
.then(function(result){
    console.log('執行2', 'result:'+result)
  return result + '22';
})
// => 執行promise1
// => 執行1 result:zqz

一旦出現非同步操作,就有問題!很明顯,Promise的主要作用就是控制非同步操作的執行順序。

肯定是哪裡有問題,我們來分析一下,非同步的時候 有哪些 不同

  • 當有非同步操作(xhr,setTimeout等)的時候,這時候PromiseStatuspending狀態

在來看下我們在pending時候的處理

...
// 非同步時
if(PromiseStatus === 'pending') {
        return new Promise(function(resolve, reject) {
         // 這裡只是將函數塞入隊列,然後就沒有然後來。。。這是有問題的
          self.resolvedCb.push(handleSuccess);
          self.rejectedCb.push(handleFail);
        })
    }
...

這時候我們的兩個數組:resolvedCbrejectedCb就發揮作用了,由於我們不知道非同步什麼時候結束,但是我們可以根據他們定義的先後順序註入到 隊列 中,然後根據 順序 依次執行,這樣也就保證了非同步操作的執行順序。

if(PromiseStatus === 'pending') {
        return new Promise(function(resolve, reject) {
         // 一個個的塞入隊列
          self.resolvedCb.push(function(result) {
              var res = handleSuccess(self.PromiseValue);
              resolve(res);
          })
          self.rejectedCb.push(function(err) {
              var er = handleFail(self.PromiseValue);
              reject(er);
          })
        })
    }

這時候我們用多個非同步操作來測試一下

function async1() {
  return new Promise(function(resolve, reject){
    // 非同步操作
    setTimeout(function(){
      console.log('執行async1')
      resolve('zqz1');
    },3000)
  })
}
function async2() {
  return new Promise(function(resolve, reject){
    // 非同步操作
    setTimeout(function(){
      console.log('執行async2')
      resolve('zqz2');
    },1000)
  })
}
function async3() {
  return new Promise(function(resolve, reject){
    // 非同步操作
    setTimeout(function(){
      console.log('執行async3')
      resolve('zqz3');
    },2000)
  })
}

// return 一個新的promise
async1().then(function(result){
   console.log('result = ' + result)
   return async2();
}).then(function(result){
   console.log('result = ' + result)
   return async3();
}).then(function(result){
   console.log('result = ' + result)
   return result;
})

// => Promise {PromiseStatus: "pending", PromiseValue: "", resolvedCb: Array(0), rejectedCb: Array(0)}
// => 執行async1
// => result = zqz1
// => result = [object Object]
// => result = [object Object]
// => 執行async2
// => 執行async3

這裡有兩個問題:

  • 返回promise的時候,執行順序有問題
  • 返回promise的時候,無法進行值的傳遞

我們再來分析下,著重看下下麵這塊代碼

...
if(PromiseStatus === 'pending') {
        return new Promise(function(resolve, reject) {
          self.resolvedCb.push(function(result) {
              // 這裡返回的res有可能是promise,但是我們沒有做處理
              var res = handleSuccess(self.PromiseValue);
              resolve(res);
          })
          self.rejectedCb.push(function(err) {
              // 這裡返回的res有可能是promise,但是我們沒有做處理
              var er = handleFail(self.PromiseValue);
              reject(er);
          })
        })
    }
...

因為我們返回的是Promise,由於我們沒有做處理,導致無法正確的獲取到值。

...
if(PromiseStatus === 'pending') {
        return new Promise(function(resolve, reject) {
          self.resolvedCb.push(function(result) {
              var res = handleSuccess(self.PromiseValue);
              if (res instanceof Promise) {
                   res.then(resolve, reject);
              } else {
                   resolve(res);
              } 
          })
          self.rejectedCb.push(function(err) {
              var er = handleFail(self.PromiseValue);
              if (er instanceof Promise) {
                   er.then(resolve, reject);
              } else {
                   reject(er);
              }
          })
        })
    }
...

如果返回的是一個Promise,就繼續塞入到then裡面。

在執行一下:

async1().then(function(result){
   console.log('result = ' + result)
   return async2();
}).then(function(result){
   console.log('result = ' + result)
   return async3();
}).then(function(result){
   console.log('result = ' + result)
   return result;
})

// => Promise {PromiseStatus: "pending", PromiseValue: "", resolvedCb: Array(0), rejectedCb: Array(0)}
// => 執行async1
// => result = zqz1
// => 執行async2
// => result = zqz2
// => 執行async3
// => result = zqz3

最後一個簡單完整的 then:

Promise.prototype.then = function (handleSuccess, handleFail) {
    var self = this;
    var PromiseStatus = this.PromiseStatus;

    if(typeof handleSuccess === 'function') {
      handleSuccess = handleSuccess;
    } else {
      handleSuccess = function (result) {}
    }

    if(typeof handleFail === 'function') {
      handleFail = handleFail;
    } else {
      handleFail = function (err) {}
    }

    if(PromiseStatus === 'pending') {
        return new Promise(function(resolve, reject) {
          self.resolvedCb.push(function(result) {
              var res = handleSuccess(self.PromiseValue);
              if (res instanceof Promise) {
                   res.then(resolve, reject);
              } else {
                  resolve(er);
              } 
          })
          self.rejectedCb.push(function(err) {
              var er = handleFail(self.PromiseValue);
              if (er instanceof Promise) {
                   er.then(resolve, reject);
              } else {
                  reject(er);
              } 
          })
        })
    }
    if(PromiseStatus === 'resolved') {
        return new Promise(function(resolve, reject) {
          var result = handleSuccess(self.PromiseValue);
          resolve(result);
        })
    }
    if(PromiseStatus === 'rejected') {
      return new Promise(function(resolve, reject) {
        var result = handleFail(self.PromiseValue);
        reject(result);
      })
    }
    
}

參考



您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 最近在看SDWebImage源碼,碰到一些比較繞的問題,理解了很久,然後在網上查了些的資料,才算是有了一些理解。在此記錄一下。 源碼如下: block會copy要在block中使用的實變數,而copy會是變數的retainCount + 1,如若在不註意很容易造成迴圈引用。而所謂的迴圈引用的本質就是 ...
  • 最近突然心血如潮開始學了Android破解,初入門,便是將經驗記錄下來。 準備工作: 1、一個登錄簡單APP 在我們破解之前,我們需要做一個簡單的登錄APP,輸入相應的賬號與密碼便是彈出登錄成功的對話框,賬號與密碼不匹配的話則是彈出登錄失敗的對話框,如下圖 由於我之前已經寫了一篇關於製作登錄界面的簡 ...
  • PS:我們在做安卓程式的時候,免不了會做一些圖形,自己可以選擇自定義view ,就是用Canvas畫,也可以用寫好的jar包,就是achartengine.jar,使用jar包的好處就快速繪製圖形,不用我們計算坐標等,計算坐標就會和數學結合,通過sin,cos等來計算,遇到3維那就更麻煩了,就像Op ...
  • PS:SharedPreferences只要稍微學過一點就會用,他本身通過創建一個Editor對象,來存儲提交,而editor可以存的格式為 他裡面可以存一個Set<String> HashSet是Java中非常常用的數據結構對象,HashSet是通過HashMap實現的,TreeSet是通過Tre ...
  • 一、概述 Glide是一個在Android端非常好的圖片緩衝工具,總體上來說,他有以下優點 使用簡單 自適應程度高 支持常見的圖片格式,如jpg,png等 支持多種數據源,網路,本地,資源,Assets等 高效的緩存策略,支持Memory和Disk圖片緩存策略,預設Bitmap格式採用RGB_565 ...
  • 時間:2017年12月10日 21:52:59 用途:此文章用於個人總結 css筆記1.CSS全稱為"層疊樣式表(Cascading Style Sheets)"2.CSS樣式的語法: 選擇符{屬性:值}3.CSS樣式代碼插入的三種形式:內聯/嵌入/外部 內聯式:就是把代碼寫在html標簽的行間樣式 ...
  • 萬事開頭難,一個好的Hello World程式可以節省我們好多的學習時間,幫助我們快速入門。Hello World程式之所以是入門必讀必會,就是因為其代碼量少,簡單易懂。但我覺得,還應該做到功能豐富,涉及的知識點多。這樣才是一個好的初學者入門指引程式。 之所以選擇Vue,不僅因為其流行,還因為其輕量 ...
  • 關於ES6模塊化 歷史上,JavaScript 一直沒有模塊(module)體系,無法將一個大程式拆分成互相依賴的小文件,再用簡單的方法拼裝起來。其他語言都有這項功能,比如 Ruby 的require、Python 的import,甚至就連 CSS 都有@import,但是 JavaScript 任 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...