這一次,徹底弄懂 Promise 原理

来源:https://www.cnblogs.com/LuckyWinty/archive/2019/09/09/11490662.html
-Advertisement-
Play Games

作者聲明 本人將遷移至個人公眾號「前端Q」及「掘金」平臺寫文章。博客園的文章將不再及時更新發佈。歡迎大家關註公眾號「前端Q」及我的掘金主頁:https://juejin.im/user/5874526761ff4b006d4fd9a4/posts Promise 必須為以下三種狀態之一:等待態(Pe ...


作者聲明

本人將遷移至個人公眾號「前端Q」及「掘金」平臺寫文章。博客園的文章將不再及時更新發佈。歡迎大家關註公眾號「前端Q」及我的掘金主頁https://juejin.im/user/5874526761ff4b006d4fd9a4/posts

 

Promise 必須為以下三種狀態之一:等待態(Pending)、執行態(Fulfilled)和拒絕態(Rejected)。一旦Promise 被 resolve 或 reject,不能再遷移至其他任何狀態(即狀態 immutable)。

基本過程:

  1. 初始化 Promise 狀態(pending)
  2. 執行 then(..) 註冊回調處理數組(then 方法可被同一個 promise 調用多次)
  3. 立即執行 Promise 中傳入的 fn 函數,將Promise 內部 resolve、reject 函數作為參數傳遞給 fn ,按事件機制時機處理
  4. Promise里的關鍵是要保證,then方法傳入的參數 onFulfilled 和 onRejected,必須在then方法被調用的那一輪事件迴圈之後的新執行棧中執行。

真正的鏈式Promise是指在當前promise達到fulfilled狀態後,即開始進行下一個promise.

鏈式調用

先從 Promise 執行結果看一下,有如下一段代碼:

    new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve({ test: 1 })
            resolve({ test: 2 })
            reject({ test: 2 })
        }, 1000)
    }).then((data) => {
        console.log('result1', data)
    },(data1)=>{
        console.log('result2',data1)
    }).then((data) => {
        console.log('result3', data)
    })
    //result1 { test: 1 }
    //result3 undefined

顯然這裡輸出了不同的 data。由此可以看出幾點:

  1. 可進行鏈式調用,且每次 then 返回了新的 Promise(2次列印結果不一致,如果是同一個實例,列印結果應該一致。
  2. 只輸出第一次 resolve 的內容,reject 的內容沒有輸出,即 Promise 是有狀態且狀態只可以由pending -> fulfilled或 pending-> rejected,是不可逆的。
  3. then 中返回了新的 Promise,但是then中註冊的回調仍然是屬於上一個 Promise 的。

基於以上幾點,我們先寫個基於 PromiseA+ 規範的只含 resolve 方法的 Promise 模型:

function Promise(fn){ 
        let state = 'pending';
        let value = null;
        const callbacks = [];

        this.then = function (onFulfilled){
            return new Promise((resolve, reject)=>{
                handle({ //橋梁,將新 Promise 的 resolve 方法,放到前一個 promise 的回調對象中
                    onFulfilled, 
                    resolve
                })
            })
        }

        function handle(callback){
            if(state === 'pending'){
                callbacks.push(callback)
                return;
            }
            
            if(state === 'fulfilled'){
                if(!callback.onFulfilled){
                    callback.resolve(value)
                    return;
                }
                const ret = callback.onFulfilled(value) //處理回調
                callback.resolve(ret) //處理下一個 promise 的resolve
            }
        }
        function resolve(newValue){
            const fn = ()=>{
                if(state !== 'pending')return

                state = 'fulfilled';
                value = newValue
                handelCb()
            }
            
            setTimeout(fn,0) //基於 PromiseA+ 規範
        }
        
        function handelCb(){
            while(callbacks.length) {
                const fulfiledFn = callbacks.shift();
                handle(fulfiledFn);
            };
        }
        
        fn(resolve)
    }

這個模型簡單易懂,這裡最關鍵的點就是在 then 中新創建的 Promise,它的狀態變為 fulfilled 的節點是在上一個 Promise的回調執行完畢的時候。也就是說當一個 Promise 的狀態被 fulfilled 之後,會執行其回調函數,而回調函數返回的結果會被當作 value,返回給下一個 Promise(也就是then 中產生的 Promise),同時下一個 Promise的狀態也會被改變(執行 resolve 或 reject),然後再去執行其回調,以此類推下去...鏈式調用的效應就出來了。

但是如果僅僅是例子中的情況,我們可以這樣寫:

 new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve({ test: 1 })
        }, 1000)
    }).then((data) => {
        console.log('result1', data)
        //dosomething
        console.log('result3')
    })
    //result1 { test: 1 }
    //result3

實際上,我們常用的鏈式調用,是用在非同步回調中,以解決"回調地獄"的問題。如下例子:

new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve({ test: 1 })
  }, 1000)
}).then((data) => {
  console.log('result1', data)
  //dosomething
  return test()
}).then((data) => {
  console.log('result2', data)
})

function test(id) {
  return new Promise(((resolve) => {
    setTimeout(() => {
      resolve({ test: 2 })
    }, 5000)
  }))
}
//基於第一個 Promise 模型,執行後的輸出
//result1 { test: 1 }
//result2 Promise {then: ƒ}

用上面的 Promise 模型,得到的結果顯然不是我們想要的。認真看上面的模型,執行 callback.resolve 時,傳入的參數是 callback.onFulfilled 執行完成的返回,顯然這個測試例子返回的就是一個 Promise,而我們的 Promise 模型中的 resolve 方法並沒有特殊處理。那麼我們將 resolve 改一下:

    function Promise(fn){ 
        ...
        function resolve(newValue){
            const fn = ()=>{
                if(state !== 'pending')return

                if(newValue && (typeof newValue === 'object' || typeof newValue === 'function')){
                    const {then} = newValue
                    if(typeof then === 'function'){
                        // newValue 為新產生的 Promise,此時resolve為上個 promise 的resolve
                        //相當於調用了新產生 Promise 的then方法,註入了上個 promise 的resolve 為其回調
                        then.call(newValue,resolve)
                        return
                    }
                }
                state = 'fulfilled';
                value = newValue
                handelCb()
            }
            
            setTimeout(fn,0)
        }
        ...
    }

用這個模型,再測試我們的例子,就得到了正確的結果:

    new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve({ test: 1 })
        }, 1000)
    }).then((data) => {
        console.log('result1', data)
        //dosomething
        return test()
    }).then((data) => {
        console.log('result2', data)
    })

    function test(id) {
        return new Promise(((resolve, reject) => {
            setTimeout(() => {
            resolve({ test: 2 })
            }, 5000)
        }))
    }
    //result1 { test: 1 }
    //result2 { test: 2 }

顯然,新增的邏輯就是針對 resolve 入參為 Promise 的時候的處理。我們觀察一下 test 裡面創建的 Promise,它是沒有調用 then方法的。從上面的分析我們已經知道 Promise 的回調函數就是通過調用其 then 方法註冊的,因此 test 裡面創建的 Promise 其回調函數為空。

顯然如果沒有回調函數,執行 resolve 的時候,是沒辦法鏈式下去的。因此,我們需要主動為其註入回調函數。

我們只要把第一個 then 中產生的 Promise 的 resolve 函數的執行,延遲到 test 裡面的 Promise 的狀態為 onFulfilled 的時候再執行,那麼鏈式就可以繼續了。所以,當 resolve 入參為 Promise 的時候,調用其 then 方法為其註入回調函數,而註入的是前一個 Promise 的 resolve 方法,所以要用 call 來綁定 this 的指向。

基於新的 Promise 模型,上面的執行過程產生的 Promise 實例及其回調函數,可以用看下表:

Promisecallback
P1 [{onFulfilled:c1(第一個then中的fn),resolve:p2resolve}]
P2 (P1 調用 then 時產生) [{onFulfilled:c2(第二個then中的fn),resolve:p3resolve}]
P3 (P2 調用 then 時產生) []
P4 (執行c1中產生[調用 test ]) [{onFulfilled:p2resolve,resolve:p5resolve}]
P5 (調用p2resolve 時,進入 then.call 邏輯中產生) []

有了這個表格,我們就可以清晰知道各個實例中 callback 執行的順序是:

c1 -> p2resolve -> c2 -> p3resolve -> [] -> p5resolve -> []

以上就是鏈式調用的原理了。

reject

下麵我們再來補全 reject 的邏輯。只需要在註冊回調、狀態改變時加上 reject 的邏輯即可。

完整代碼如下:

   function Promise(fn){ 
        let state = 'pending';
        let value = null;
        const callbacks = [];

        this.then = function (onFulfilled,onRejected){
            return new Promise((resolve, reject)=>{
                handle({
                    onFulfilled, 
                    onRejected,
                    resolve, 
                    reject
                })
            })
        }

        function handle(callback){
            if(state === 'pending'){
                callbacks.push(callback)
                return;
            }
            
            const cb = state === 'fulfilled' ? callback.onFulfilled:callback.onRejected;
            const next = state === 'fulfilled'? callback.resolve:callback.reject;

            if(!cb){
                next(value)
                return;
            }
            const ret = cb(value)
            next(ret)
        }
        function resolve(newValue){
            const fn = ()=>{
                if(state !== 'pending')return

                if(newValue && (typeof newValue === 'object' || typeof newValue === 'function')){
                    const {then} = newValue
                    if(typeof then === 'function'){
                        // newValue 為新產生的 Promise,此時resolve為上個 promise 的resolve
                        //相當於調用了新產生 Promise 的then方法,註入了上個 promise 的resolve 為其回調
                        then.call(newValue,resolve, reject)
                        return
                    }
                }
                state = 'fulfilled';
                value = newValue
                handelCb()
            }
            
            setTimeout(fn,0)
        }
        function reject(error){

            const fn = ()=>{
                if(state !== 'pending')return

                if(error && (typeof error === 'object' || typeof error === 'function')){
                    const {then} = error
                    if(typeof then === 'function'){
                        then.call(error,resolve, reject)
                        return
                    }
                }
                state = 'rejected';
                value = error
                handelCb()
            }
            setTimeout(fn,0)
        }
        function handelCb(){
            while(callbacks.length) {
                const fn = callbacks.shift();
                handle(fn);
            };
        }
        fn(resolve, reject)
    }

異常處理

異常通常是指在執行成功/失敗回調時代碼出錯產生的錯誤,對於這類異常,我們使用 try-catch 來捕獲錯誤,並將 Promise 設為 rejected 狀態即可。

handle代碼改造如下:

    function handle(callback){
        if(state === 'pending'){
            callbacks.push(callback)
            return;
        }
        
        const cb = state === 'fulfilled' ? callback.onFulfilled:callback.onRejected;
        const next = state === 'fulfilled'? callback.resolve:callback.reject;

        if(!cb){
            next(value)
            return;
        }
        try {
            const ret = cb(value)
            next(ret)
        } catch (e) {
            callback.reject(e);
        }  
    }

我們實際使用時,常習慣註冊 catch 方法來處理錯誤,例:

    new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve({ test: 1 })
        }, 1000)
    }).then((data) => {
        console.log('result1', data)
        //dosomething
        return test()
    }).catch((ex) => {
        console.log('error', ex)
    })

實際上,錯誤也好,異常也罷,最終都是通過reject實現的。也就是說可以通過 then 中的錯誤回調來處理。所以我們可以增加這樣的一個 catch 方法:

    function Promise(fn){ 
        ...
        this.then = function (onFulfilled,onRejected){
            return new Promise((resolve, reject)=>{
                handle({
                    onFulfilled, 
                    onRejected,
                    resolve, 
                    reject
                })
            })
        }
        this.catch = function (onError){
            this.then(null,onError)
        }
        ...
    }

Finally方法

在實際應用的時候,我們很容易會碰到這樣的場景,不管Promise最後的狀態如何,都要執行一些最後的操作。我們把這些操作放到 finally 中,也就是說 finally 註冊的函數是與 Promise 的狀態無關的,不依賴 Promise 的執行結果。所以我們可以這樣寫 finally 的邏輯:

function Promise(fn){ 
        ...
        this.catch = function (onError){
            this.then(null,onError)
        }
        this.finally = function (onDone){
            this.then(onDone,onError)
        }
        ...
    }

resolve 方法和 reject 方法

實際應用中,我們可以使用 Promise.resolve 和 Promise.reject 方法,用於將於將非 Promise 實例包裝為 Promise 實例。如下例子:

Promise.resolve({name:'winty'})
Promise.reject({name:'winty'})
// 等價於
new Promise(resolve => resolve({name:'winty'}))
new Promise((resolve,reject) => reject({name:'winty'}))

這些情況下,Promise.resolve 的入參可能有以下幾種情況:

  • 無參數 [直接返回一個resolved狀態的 Promise 對象]
  • 普通數據對象 [直接返回一個resolved狀態的 Promise 對象]
  • 一個Promise實例 [直接返回當前實例]
  • 一個thenable對象(thenable對象指的是具有then方法的對象) [轉為 Promise 對象,並立即執行thenable對象的then方法。]

基於以上幾點,我們可以實現一個 Promise.resolve 方法如下:

 function Promise(fn){ 
        ...
        this.resolve = function (value){
            if (value && value instanceof Promise) {
                return value;
            } else if (value && typeof value === 'object' && typeof value.then === 'function'){
                let then = value.then;
                return new Promise(resolve => {
                    then(resolve);
                });
            } else if (value) {
                return new Promise(resolve => resolve(value));
            } else {
                return new Promise(resolve => resolve());
            }
        }
        ...
    }

Promise.reject與Promise.resolve類似,區別在於Promise.reject始終返回一個狀態的rejected的Promise實例,而Promise.resolve的參數如果是一個Promise實例的話,返回的是參數對應的Promise實例,所以狀態不一 定。 因此,reject 的實現就簡單多了,如下:

function Promise(fn){ 
        ...
        this.reject = function (value){
            return new Promise(function(resolve, reject) {
                reject(value);
            });
        }
        ...
    }

Promise.all

入參是一個 Promise 的實例數組,然後註冊一個 then 方法,然後是數組中的 Promise 實例的狀態都轉為 fulfilled 之後則執行 then 方法。這裡主要就是一個計數邏輯,每當一個 Promise 的狀態變為 fulfilled 之後就保存該實例返回的數據,然後將計數減一,當計數器變為 0 時,代表數組中所有 Promise 實例都執行完畢。

    function Promise(fn){ 
        ...
        this.all = function (arr){
            var args = Array.prototype.slice.call(arr);
            return new Promise(function(resolve, reject) {
                if(args.length === 0) return resolve([]);
                var remaining = args.length;

                function res(i, val) {
                    try {
                        if(val && (typeof val === 'object' || typeof val === 'function')) {
                            var then = val.then;
                            if(typeof then === 'function') {
                                then.call(val, function(val) {
                                    res(i, val);
                                }, reject);
                                return;
                            }
                        }
                        args[i] = val;
                        if(--remaining === 0) {
                            resolve(args);
                        }
                    } catch(ex) {
                        reject(ex);
                    }
                }
                for(var i = 0; i < args.length; i++) {
                    res(i, args[i]);
                }
            });
        }
        ...
    }

Promise.race

有了 Promise.all 的理解,Promise.race 理解起來就更容易了。它的入參也是一個 Promise 實例數組,然後其 then 註冊的回調方法是數組中的某一個 Promise 的狀態變為 fulfilled 的時候就執行。因為 Promise 的狀態只能改變一次,那麼我們只需要把 Promise.race 中產生的 Promise 對象的 resolve 方法,註入到數組中的每一個 Promise 實例中的回調函數中即可。

function Promise(fn){ 
    ...
    this.race = function(values) {
        return new Promise(function(resolve, reject) {
            for(var i = 0, len = values.length; i < len; i++) {
                values[i].then(resolve, reject);
            }
        });
    }
    ...
    }  

總結

Promise 源碼不過幾百行,我們可以從執行結果出發,分析每一步的執行過程,然後思考其作用即可。其中最關鍵的點就是要理解 then 函數是負責註冊回調的,真正的執行是在 Promise 的狀態被改變之後。而當 resolve 的入參是一個 Promise 時,要想鏈式調用起來,就必須調用其 then 方法(then.call),將上一個 Promise 的 resolve 方法註入其回調數組中。

參考資料

完整 Promise 模型

function Promise(fn) {
  let state = 'pending'
  let value = null
  const callbacks = []

  this.then = function (onFulfilled, onRejected) {
    return new Promise((resolve, reject) => {
      handle({
        onFulfilled,
        onRejected,
        resolve,
        reject,
      })
    })
  }

  this.catch = function (onError) {
    this.then(null, onError)
  }

  this.finally = function (onDone) {
    this.then(onDone, onError)
  }

  this.resolve = function (value) {
    if (value && value instanceof Promise) {
      return value
    } if (value && typeof value === 'object' && typeof value.then === 'function') {
      const { then } = value
      return new Promise((resolve) => {
        then(resolve)
      })
    } if (value) {
      return new Promise(resolve => resolve(value))
    }
    return new Promise(resolve => resolve())
  }

  this.reject = function (value) {
    return new Promise(((resolve, reject) => {
      reject(value)
    }))
  }

  this.all = function (arr) {
    const args = Array.prototype.slice.call(arr)
    return new Promise(((resolve, reject) => {
      if (args.length === 0) return resolve([])
      let remaining = args.length

      function res(i, val) {
        try {
          if (val && (typeof val === 'object' || typeof val === 'function')) {
            const { then } = val
            if (typeof then === 'function') {
              then.call(val, (val) => {
                res(i, val)
              }, reject)
              return
            }
          }
          args[i] = val
          if (--remaining === 0) {
            resolve(args)
          }
        } catch (ex) {
          reject(ex)
        }
      }
      for (let i = 0; i < args.length; i++) {
        res(i, args[i])
      }
    }))
  }

  this.race = function (values) {
    return new Promise(((resolve, reject) => {
      for (let i = 0, len = values.length; i < len; i++) {
        values[i].then(resolve, reject)
      }
    }))
  }

  function handle(callback) {
    if (state === 'pending') {
      callbacks.push(callback)
      return
    }

    const cb = state === 'fulfilled' ? callback.onFulfilled : callback.onRejected
    const next = state === 'fulfilled' ? callback.resolve : callback.reject

    if (!cb) {
      next(value)
      return
    }
    try {
      const ret = cb(value)
      next(ret)
    } catch (e) {
      callback.reject(e)
    }
  }
  function resolve(newValue) {
    const fn = () => {
      if (state !== 'pending') return

      if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {
        const { then } = newValue
        if (typeof then === 'function') {
          // newValue 為新產生的 Promise,此時resolve為上個 promise 的resolve
          // 相當於調用了新產生 Promise 的then方法,註入了上個 promise 的resolve 為其回調
          then.call(newValue, resolve, reject)
          return
        }
      }
      state = 'fulfilled'
      value = newValue
      handelCb()
    }

    setTimeout(fn, 0)
  }
  function reject(error) {
    const fn = () => {
      if (state !== 'pending') return

      if (error && (typeof error === 'object' || typeof error === 'function')) {
        const { then } = error
        if (typeof then === 'function') {
          then.call(error, resolve, reject)
          return
        }
      }
      state = 'rejected'
      value = error
      handelCb()
    }
    setTimeout(fn, 0)
  }
  function handelCb() {
    while (callbacks.length) {
      const fn = callbacks.shift()
      handle(fn)
    }
  }
  fn(resolve, reject)
}

最後

覺得內容有幫助可以關註下我的掘金主頁https://juejin.im/user/5874526761ff4b006d4fd9a4/posts

覺得內容有幫助可以關註下我的公眾號 「前端Q」,一起學習成長~~




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

-Advertisement-
Play Games
更多相關文章
  • 對於目前的圖片懶載入,我們一般採用的是通過第三方庫或懶載入庫來實現,但是該方式的顯著問題就是,必須按順序執行: 1、載入初始的 HTML 響應內容 2、載入懶載入庫 3、載入圖片 假如瀏覽器能直接支持懶載入,那是最好的,這一想法也不是不可能哦!從Chrome 75開始,我們可以通過切換兩個開關來手動 ...
  • 日常後臺迭代開發,需要在瀏覽器刷新時記錄下當前tab和翻頁數,用的elementUI。 其實數據記錄倒是挺簡單的,localstorage或者sessionstorage都行,但在組件生命周期鉤子func:created中設置pagination的current-page時,不生效。 嘗試過vm.$ ...
  • 對於jQuery的調用,我們一般都會傳入參數 一、前置(DOM對象和jQuery對象) 1. DOM對象, 關於這個,應該是前端的基礎知識了,在文檔對象模型中,每個部分都是節點。 2. jQuery對象 這個是指通過jQuery構造函數創建出來的對象,可以通過jQuery選擇器獲取到,並以類數組的形 ...
  • HTML介紹 Web服務本質 瀏覽器發請求 HTTP協議 服務端接收請求 服務端返迴響應 服務端把HTML文件內容發給瀏覽器 瀏覽器渲染頁面 HTML是什麼? 超文本標記語言(Hypertext Markup Language, HTML)是一種用於創建網頁的標記語言。 本質上是瀏覽器可識別的規則, ...
  • 這是一款集截屏(支持自定義尺寸)、屏幕錄影機、屏幕拾色器、屏幕放大鏡、屏幕標尺、PDF圖片轉換、圖片編輯等等諸多實用功能於一身的小工具, 完全免費,極限輕量化,軟體總體積僅1.63 MB . 鏈接: https://pan.baidu.com/s/15Mii0BiLcD3Pr2_OXAkAWw 提取 ...
  • 基於layui框架製作精美的省市區下拉框三級聯動菜單選擇, 支持三級聯動城市選擇,點擊提交獲取選中值代碼。 示例圖如下: 資源鏈接: https://pan.baidu.com/s/1s6l8iDBEP1UOt28O-5jVLg 提取碼: y4d3 標簽:三級聯動 省市區 選擇 中值 城市 下拉框 ...
  • 摘要: 很有意思的操作... 原文: "web頁面錄屏實現" 譯者:frontdog "Fundebug" 經授權轉載,版權歸原作者所有。 寫在前面的話 在看到評論後,突然意識到自己沒有提前說明,本文可以說是一篇調研學習文,是我自己感覺可行的一套方案,後續會去讀讀已經開源的一些類似的代碼庫,補足自己 ...
  • 前端需要性能優化麽? 性能優化一直以來都是前端工程領域中的一個重要部分。很多資料表明,網站應用的性能優化對於提高用戶留存、轉化率等都有積極影響。可以理解為,提升你的網站性能,就是提升你的業務數據(甚至是業務收入)。 性能優化廣義上包含前端優化和後端優化。後端優化的關註點更多的時候是在增加資源利用率、 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...