0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

Promise規(guī)范與原理解析

OSC開源社區(qū) ? 來源:OSC開源社區(qū) ? 2023-12-05 15:49 ? 次閱讀

摘要

Promise 對象用于清晰的處理異步任務(wù)的完成,返回最終的結(jié)果值,本次分享主要介紹 Promise 的基本屬性以及 Promise 內(nèi)部的基礎(chǔ)實現(xiàn),能夠幫我們更明確使用場景、更快速定位問題。

Promise 出現(xiàn)的原因

首先我們先來看一段代碼:異步請求的層層嵌套
function fn1(params) {
  const xmlHttp = new XMLHttpRequest();
  xmlHttp.onreadystatechange = function(){
    if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
      const fn1Data = {name: 'fn1'}
      console.log(fn1Data, 'fn1Data');
      // 請求2
      (function fn2() {
        xmlHttp.onreadystatechange = function(){
        if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
          const fn2Data = {name: `${fn1Data.name}-fn2`}
          console.log(fn2Data, 'fn2Data');
          // 請求3
          (function fn2() {
            xmlHttp.onreadystatechange = function(){
            if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
              const fn3Data = {name: `${fn2Data.name}-fn3`}
              console.log(fn3Data, 'fn3Data');
            }
          }
          xmlHttp.open("GET","https://v0.yiketianqi.com/api?unescape=1&version=v61", true);
          xmlHttp.send();
          })()
        }
      }
      xmlHttp.open("GET","https://v0.yiketianqi.com/api?unescape=1&version=v61", true);
      xmlHttp.send();
      })()
    }
  }
  xmlHttp.open("GET","https://v0.yiketianqi.com/api?unescape=1&version=v61", true);
  xmlHttp.send();
}

fn1()

或者我們可以將上面的代碼優(yōu)化為下面這樣

function fn1(params) {
  console.log(`我是fn1,我在函數(shù)${params}中執(zhí)行?。?!`);
}
  
function fn2(params) {
  try {
    const xmlHttp = new XMLHttpRequest();
    xmlHttp.onreadystatechange = function(){
      if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
        console.log(`我是fn2,我在函數(shù)${params}中執(zhí)行?。?!結(jié)果是:`,params.data);
        fn1('fn2')
      }
    }
    xmlHttp.open("GET","https://v0.yiketianqi.com/api?unescape=1&version=v61", true);
    xmlHttp.send();
  } catch (error) {
    console.error(error);
  }
}
  
function fn3() {
  try {
    const xmlHttp = new XMLHttpRequest();
    xmlHttp.onreadystatechange = function(){
      if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
          console.log('fn3請求已完成');
          fn2('fn3')
      }
    }
    xmlHttp.open("GET","https://v0.yiketianqi.com/api?unescape=1&version=v61", true);
    xmlHttp.send();
    console.log('我是f3函數(shù)呀');
  } catch (error) {
    console.error(error);
  }
}
  
fn3()

由上面的兩種寫法的請求可見,在 promise 之前,為了進行多個異步請求并且依賴上一個異步請求的結(jié)果時,我們必須進行層層嵌套,大多數(shù)情況下,我們又對異步結(jié)果進行數(shù)據(jù)處理,這樣使得我們的代碼非常難看,并且難以維護,這就形成了回調(diào)地獄,由此 Promise 開始出現(xiàn)了。回調(diào)地獄缺點
  • 代碼臃腫

  • 可讀性差

  • 耦合性高

  • 不好進行異常處理

Promise 的基本概念

含義

  1. ES6 將其寫進了語言標準里統(tǒng)一了用法,是一個構(gòu)造函數(shù),用來生成 Promise 實例

  2. 參數(shù)為一個執(zhí)行器函數(shù) (執(zhí)行器函數(shù)是立即執(zhí)行的), 該函數(shù)有兩個函數(shù)作為參數(shù),第一個參數(shù)是成功時的回調(diào),第二個參數(shù)是失敗時的回調(diào)

  3. 函數(shù)的方法有 resolve (可以處理成功和失敗)、reject (只處理失敗)、all 等方法

  4. then、catch、finally 方法為 Promise 實例上的方法

狀態(tài)

  1. pending --- 等待狀態(tài)

  2. Fulfilled --- 執(zhí)行狀態(tài) (resolve 回調(diào)函數(shù),then)

  3. Rejected --- 拒絕狀態(tài) (reject 回調(diào)函數(shù),catch)

  4. 狀態(tài)一旦改變就不會再變,狀態(tài)只可能是兩種改變,從 pending->Fulfilled,pending->Rejected

  5. 有兩個關(guān)鍵的屬性:PromiseState --- 狀態(tài)改變,PromiseResult --- 結(jié)果數(shù)據(jù)改變

const p1 = Promise.resolve(64)
const p2 = Promise.reject('我錯了')
const p3 = Promise.then()
const p4 = Promise.catch()

// 狀態(tài)改變PromiseState 結(jié)果改變PromiseResult
console.log(new Promise(()=>{}), 'Promise');  // PromiseState='pending' PromiseResult=undefined
console.log(p1,'p1');  // PromiseState='Fulfilled' PromiseResult=64
console.log(p2,'p2');  // PromiseState="Rejected" PromiseResult='我錯了'
console.log(p3, 'p3'); // then為實例上的方法,報錯
console.log(p4, 'p4');  // catch為實例上的方法,報錯
a047ff72-9296-11ee-939d-92fbcf53809c.png?特點1.錯誤信息清晰定位:可以在外層捕獲異常信息(網(wǎng)絡(luò)錯誤、語法錯誤都可以捕獲),有 “冒泡” 性質(zhì),會一直向后傳遞,直到被捕獲,所以在最后寫一個 catch 就可以了2.鏈式調(diào)用:每一個 then 和 catch 都會返回一個新的 Promise,把結(jié)果傳遞到下一個 then/catch 中,因此可以進行鏈式調(diào)用 --- 代碼簡潔清晰

結(jié)果由什么決定

resolve

  1. 如果傳遞的參數(shù)是非 Promise 類型的對象,則返回的結(jié)果是成功狀態(tài)的 Promise 對象,進入下一個 then 里面

  2. 如果傳遞的參數(shù)是 Promise 類型的對象,則返回的結(jié)果由返回的 Promise 決定,如果返回的是 resolve 則是成功的狀態(tài),進入下一個 then 里,如果返回的是 reject 則是失敗的狀態(tài),進入下一個 catch 里

reject

  1. 如果傳遞的參數(shù)是非 Promise 類型的對象,則返回的結(jié)果是拒絕狀態(tài)的 Promise 對象,進入下一個 catch 里面或者是下一個 then 的第二個參數(shù) reject 回調(diào)里面

  2. 如果傳遞的參數(shù)是 Promise 類型的對象,則返回的結(jié)果由返回的 Promise 決定,如果返回的是 resolve 則是成功的狀態(tài),進入下一個 then 里,如果返回的是 reject 則是拒絕的狀態(tài),進入下一個 catch 里面或者是下一個 then 的第二個參數(shù) reject 回調(diào)里面

這在我們自己封裝的 API 里面也有體現(xiàn):為什么 code 為 1 時都是 then 接收,其他都是 catch 接收,就是因為在 then 里面也就是 resolve 函數(shù)中對 code 碼進行了判斷,如果是 1 則返回 Promise.resolve (),進入 then 里處理,如果是非 1 則返回 Promise.reject (),進入 catch 里處理。

流程圖

a064244a-9296-11ee-939d-92fbcf53809c.png?簡單使用
// 模擬一個promise的get請求
let count = 0
function customGet(url){
    count += 1
    return new Promise((resolve, reject)=>{
        const xmlHttp = new XMLHttpRequest();
        xmlHttp.open("GET",url, true);
        xmlHttp.onload = ()=>{
          console.log(xmlHttp, 'xmlHttp---onload');
          if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
            console.log('customGet請求成功了');
            // 返回非Promise,結(jié)果為成功狀態(tài)
            resolve({data:`第${count}次請求獲取數(shù)據(jù)成功`})

            // 返回Promise,結(jié)果由Promise決定
            // resolve(Promise.reject('resolve中返回reject'))
          } else {
            reject('customGet請求錯誤了')
          }
        }

        // Promise狀態(tài)改變就不會再變
        // onreadystatechange方法會被執(zhí)行四次
        // 當?shù)卮芜M來的時候,readyState不等于4,執(zhí)行else邏輯,執(zhí)行reject,狀態(tài)變?yōu)镽ejected,所以即使再執(zhí)行if,狀態(tài)之后不會再改變
        // xmlHttp.onreadystatechange = function(){
        //   console.log(xmlHttp,'xmlHttp---onreadystatechange')
        //   if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
        //     console.log('customGet請求成功了');
        //     resolve({data:`第${count}次請求獲取數(shù)據(jù)成功`})
        //   } else {
        //     reject('customGet請求錯誤了')
        //   }
        // }
        xmlHttp.send();
      })
 }

// 使用Promise,并且進行鏈式調(diào)用
customGet('https://v0.yiketianqi.com/api/cityall?appid=&appsecret=').then((res)=>{
   console.log(res.data);
   return '第一次請求處理后的數(shù)據(jù)'
}).then((data)=>{
   console.log(data)
   // console.log(data.toFixed());
   return customGet('https://v0.yiketianqi.com/api/cityall?appid=&appsecret=')
}).then((res)=>{
   console.log(res.data);
}).catch((err)=>{
    // 以類似'冒泡'的性質(zhì)再外層捕獲所有的錯誤
   console.error(err, '這是catch里的錯誤信息');
})

手寫實現(xiàn)簡單的 Promise通過上面的回顧,我們已經(jīng)了解了 Promise 的關(guān)鍵屬性和特點,下面我們一起來實現(xiàn)一個簡單的 Promise 吧
  // 1、封裝一個Promise構(gòu)造函數(shù),有一個函數(shù)參數(shù)
  function Promise(executor){
    // 7、添加對象屬性PromiseState PromiseResult
    this.PromiseState = 'pending'
    this.PromiseResult = null

    // 14、創(chuàng)建一個保存成功失敗回調(diào)函數(shù)的屬性
    this.callback = null

    // 8、this指向問題
    const that = this

    // 4、executor有兩個函數(shù)參數(shù)(resolve,reject)
    function resolve(data){
      // 10、Promise狀態(tài)只能修改一次(同時記得處理reject中的狀態(tài))
      if(that.PromiseState !== 'pending') return

      // console.log(this, 'this');
      // 5、修改對象的狀態(tài)PromiseState
      that.PromiseState = 'Fulfilled'

      // 6、修改對象的結(jié)果PromiseResult
      that.PromiseResult = data

      // 15、異步執(zhí)行then里的回調(diào)函數(shù)
      if(that.callback?.onResolve){
        that.callback.onResolve(that.PromiseResult)
      }
    }
    function reject(data){
      console.log(that.PromiseState, 'that.PromiseState');
      if(that.PromiseState !== 'pending') return

      // 9、處理失敗函數(shù)狀態(tài)
      that.PromiseState = 'Rejected'
      that.PromiseResult = data
      console.log(that.PromiseResult, 'that.PromiseResult');
      console.log(that.PromiseState, 'that.PromiseState');

      // 16、異步執(zhí)行then里的回調(diào)函數(shù)
      if(that.callback?.onReject){
        that.callback.onReject(that.PromiseResult)
      }
    }
    // 3、執(zhí)行器函數(shù)是同步調(diào)用的,并且有兩個函數(shù)參數(shù)
    executor(resolve,reject)
  }
  // 2、函數(shù)的實例上有方法then
  Promise.prototype.then = function(onResolve,onReject){
    // 20、處理onReject沒有的情況
    if(typeof onReject !== 'function'){
      onReject = reason => {
        throw reason
      }
    }
    // 21、處理onResolve沒有的情況
    if(typeof onResolve !== 'function'){
      onResolve = value => value
    }
    // 17、每一個then方法都返回一個新的Promise,并且把上一個then返回的結(jié)果傳遞出去
    return new Promise((nextResolve,nextReject)=>{
      // 11、處理成功或失敗
      if(this.PromiseState === 'Fulfilled'){
        // 12、將結(jié)果傳遞給函數(shù)
        // onResolve(this.PromiseResult)

        // 18、拿到上一次執(zhí)行完后返回的結(jié)果,判斷是不是Promise
        const result = onResolve(this.PromiseResult)
        if(result instanceof Promise){
          result.then((v)=>{
            nextResolve(v)
          },(r)=>{
            nextReject(r)
          })
        } else {
          nextResolve(result)
        }
      }
      // 當你一步步寫下來的時候有沒有懷疑過為什么不用else
       if(this.PromiseState === 'Rejected'){
            // 第12步同時處理此邏輯
            // onReject(this.PromiseResult)

            // 22、處理catch異常穿透捕獲錯誤
            try {
              const result = onReject(this.PromiseResult)
              if(result instanceof Promise){
                result.then((v)=>{
                  nextResolve(v)
                }).catch((r)=>{
                  nextReject(r)
                })
              } else {
                nextReject(result)
              }
            } catch (error) {
              nextReject(this.PromiseResult)
            }
         }
  
      // 13、異步任務(wù)時處理成功或失敗,想辦法等異步任務(wù)執(zhí)行完成后才去執(zhí)行這兩個函數(shù)
      if(this.PromiseState === 'pending'){
        this.callback = {
          onResolve,
          onReject
        }
        console.log(this.callback, 'this.callback');
      }
    })
  }
  // 19、函數(shù)實例上有方法catch
  Promise.prototype.catch = function(onReject) {
    return this.then(null,onReject)
  }

  // 使用自定義封裝的Promise
  const customP = new Promise((resolve,reject)=>{
    // 模擬異步執(zhí)行請求
    // const xmlHttp = new XMLHttpRequest();
    // xmlHttp.open("GET",'https://v0.yiketianqi.com/api/cityall?appid=&appsecret=', true);
    // xmlHttp.onload = ()=>{
    //   if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
    //     resolve('success')
    //   } else {
    //     reject('error')
    //   }
    // }
    // xmlHttp.send();

    // 同步執(zhí)行
    resolve('success')
    // reject('error')
  })

  console.log(customP, 'customP');
  customP.then((res)=>{
    console.log(res, 'resolve回調(diào)');
    return '第一次回調(diào)'
    // return new Promise((resolve,reject)=>{
    //   reject('錯錯錯')
    // })
  },(err)=>{
    console.error(err, 'reject回調(diào)');
    return '2121'
  }).then(()=>{
    console.log('then里面輸出');
  }).then().catch((err)=>{
    console.error(err, 'catch里的錯誤');
  })

針對 resolve 中返回 Promise 對象時的內(nèi)部執(zhí)行順序a076ff7a-9296-11ee-939d-92fbcf53809c.png??總結(jié)以上就是我們常用的 Promise 基礎(chǔ)實現(xiàn),在實現(xiàn)過程中對比了 Promise 和函數(shù)嵌套處理異步請求的優(yōu)缺點,Promise 仍存在缺點,但是的確方便很多,同時更清晰的理解到錯誤處理如何進行異常穿透的,也能幫助我們更規(guī)范的使用 Promise 以及快速定位問題所在。

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學習之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 耦合
    +關(guān)注

    關(guān)注

    13

    文章

    578

    瀏覽量

    100770
  • 代碼
    +關(guān)注

    關(guān)注

    30

    文章

    4728

    瀏覽量

    68253
  • 執(zhí)行器
    +關(guān)注

    關(guān)注

    5

    文章

    375

    瀏覽量

    19295

原文標題:Promise規(guī)范與原理解析

文章出處:【微信號:OSC開源社區(qū),微信公眾號:OSC開源社區(qū)】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    鴻蒙原生應(yīng)用開發(fā)-ArkTS語言基礎(chǔ)類庫異步并發(fā)簡述Promise

    Promise和async/await提供異步并發(fā)能力,是標準的JS異步語法。異步代碼會被掛起并在之后繼續(xù)執(zhí)行,同一時間只有一段代碼執(zhí)行,適用于單次I/O任務(wù)的場景開發(fā),例如一次網(wǎng)絡(luò)請求、一次文件
    發(fā)表于 03-07 15:46

    手機通信原理解析

    `手機通信原理解析:第 1 章    無線通信原理第2 章    移動通信系統(tǒng)第3 章    移動通信系統(tǒng)的多址接入技術(shù)第4 章    移動通信系統(tǒng)的語音編碼第5 章 GSM移動通信系統(tǒng)的數(shù)字
    發(fā)表于 12-14 14:31

    定位技術(shù)原理解析

    【追蹤嫌犯的利器】定位技術(shù)原理解析(4)
    發(fā)表于 05-04 12:20

    Promise對象的基礎(chǔ)知識

    Promise 對象學習筆記
    發(fā)表于 06-04 16:19

    如何使用abortController終止fetch和promise?

    使用abortController 終止fetch和promise的方法
    發(fā)表于 11-05 08:07

    鋰電池基本原理解析

    【鋰知道】鋰電池基本原理解析:充電及放電機制電池充電最重要的就是這三步:第一步:判斷電壓
    發(fā)表于 09-15 06:47

    如何更好地理解各種抖動技術(shù)規(guī)范

    今天,我將幫助您了解如何更好地理解各種抖動技術(shù)規(guī)范。隨著高速應(yīng)用中的定時要求日趨嚴格,對各種抖動技術(shù)規(guī)范的更深入理解現(xiàn)已變得非常重要。從 10Gb 以太網(wǎng)網(wǎng)絡(luò)到 PCIe 等高速互聯(lián)技
    發(fā)表于 11-21 06:02

    虛擬存儲器部件原理解析

    虛擬存儲器部件原理解析
    發(fā)表于 04-15 14:25 ?3100次閱讀

    混合動力電動汽車的電池管理解析

    混合動力電動汽車的電池管理解析 一個電池規(guī)定的容量是指電池從100%充電狀態(tài)到零充電狀態(tài)所能提供的電量。充電到100%充電狀態(tài)或放電到零充電狀
    發(fā)表于 05-12 17:51 ?2818次閱讀
    混合動力電動汽車的電池管<b class='flag-5'>理解析</b>

    觸摸屏的應(yīng)用與工作原理解析

    觸摸屏的應(yīng)用與工作原理解析
    發(fā)表于 02-08 02:13 ?38次下載

    如何更好的理解抖動技術(shù)規(guī)范

    歡迎繼續(xù)關(guān)注《定時決定一切》系列文章!上次我們探討了對 PLL 環(huán)路濾波器響應(yīng)的理解。今天,我將幫助您了解如何更好地理解各種抖動技術(shù)規(guī)范。隨著高速應(yīng)用中的定時要求日趨嚴格,對各種抖動技術(shù)規(guī)范
    發(fā)表于 04-08 04:56 ?928次閱讀
    如何更好的<b class='flag-5'>理解</b>抖動技術(shù)<b class='flag-5'>規(guī)范</b>?

    CF210SP型調(diào)頻調(diào)幅收音機電路圖及原理解析

    CF210SP型調(diào)頻調(diào)幅收音機電路圖及原理解析
    發(fā)表于 01-25 10:46 ?105次下載

    史密斯圓圖和阻抗匹配原理解析

    史密斯圓圖和阻抗匹配原理解析
    的頭像 發(fā)表于 11-02 20:16 ?1940次閱讀

    什么是晶振 晶振工作原理解析

    什么是晶振 晶振工作原理解析
    的頭像 發(fā)表于 12-30 17:13 ?4249次閱讀
    什么是晶振 晶振工作原<b class='flag-5'>理解析</b>

    電磁屏蔽技術(shù)的原理解析

    電磁屏蔽技術(shù)的原理解析 電磁屏蔽技術(shù)是一種利用特定材料或構(gòu)造來阻擋、吸收或反射外界電磁波的技術(shù)。它在電子設(shè)備、通信系統(tǒng)以及電磁環(huán)境的凈化等方面具有重要應(yīng)用,可以有效地防止電磁干擾,保護設(shè)備和人員
    的頭像 發(fā)表于 03-06 14:58 ?1795次閱讀