理解javaScript非同步

来源:https://www.cnblogs.com/yuejucai/archive/2022/05/13/16266589.html
-Advertisement-
Play Games

最近碰到了非同步編程的問題,決定從原理開始重新擼一遍,徹底弄懂非同步編程。 1.非同步編程思想 非同步編程是為瞭解決同步模式的一些痛點,同步模式中任務是依次執行,後一個任務必須要等待前一個任務結束後才能開始執行,當某個函數耗時過長時就可能造成頁面的假死和卡頓,而非同步編程中,後一個任務不會去等待前一個任務結束 ...


最近碰到了非同步編程的問題,決定從原理開始重新擼一遍,徹底弄懂非同步編程。

1.非同步編程思想

非同步編程是為瞭解決同步模式的一些痛點,同步模式中任務是依次執行,後一個任務必須要等待前一個任務結束後才能開始執行,當某個函數耗時過長時就可能造成頁面的假死和卡頓,而非同步編程中,後一個任務不會去等待前一個任務結束後才開始,當前一個任務開啟過後就立即往後執行下一個任務。耗時函數的後續邏輯會通過回調函數的方式定義。在內部,耗時任務完成過後就會自動執行傳入的回調函數。

2.同步與非同步

同步行為對應記憶體中順序執行的處理器指令,每條指令都會嚴格按照出現的順序來執行,而每條指令執行後也能立即獲得儲存在系統本地的信息.這樣的執行流程容易分析程式在執行到代碼任意位置時的狀態.
如下例子:

///同步模式
console.log('global begin')
function bar () {
    console.log('bar task') 
}
function foo () {
    console.log('foo task')
    bar()
}
foo()
console.log('global end')

// 程式列印輸出:
// global begin
// foo task
// bar task
// global end

在程式執行的每一步都可以推斷程式的狀態,因為後面的指令需要前面的完成後才執行.等到最後一條指令執行完畢,存儲在X的值就可以立即使用.這兩行代碼首先操作系統會在棧記憶體上分配一個儲存浮點數值的空間,然後針對這個值做一次數學計算,再把計算結果寫回之前分配的記憶體中.這些指令都是單線程中按順序執行的.

非同步行為就是下達這個任務開啟的指令之後代碼就會繼續執行,代碼不會等待任務的結束,如下例子:

// 非同步模式
console.log('global begin')
// 延時器
setTimeout(function timer1 () {
    console.log('timer1 invoke')
}, 1800)
// 延時器中又嵌套了一個延時器
setTimeout(function timer2 () {
    console.log('timer2 invoke')
    setTimeout(function inner () {
        console.log('inner invoke')
    }, 500)
}, 1000)
console.log('global end')

// global begin
// global end
// timer2 invoke
// inner invoke
// timer1 invoke

3.以往的非同步編程模式

在早期的js中只支持定義回調函數來表明非同步操作的完成.串聯多個非同步操作是一個常見的問題,通常需要深度嵌套的回調函數來解決,這樣會造成回調地獄。

回調函數的理解:某個方法a自己沒調用,但是在另一個方法b被調用的時候,順便把方法a給調用了,那麼方法a就是一個回調函數

 function double(value, callback) {
            setTimeout(() => {
                callback(value * 2)
            }, 2000)
}
 double(3, (x) => { console.log(`回來的數值為:${x}`); })
  //回來的數值為:6
  //會在2000毫秒後列印

這個的setTimeout調用告訴js運行時在2000毫秒後把一個函數推到消息隊列.這個函數會由運行時負責非同步調用執行,而位於函數閉包中的回調及其參數在非同步執行時仍然時可用的.

4.嵌套非同步回調(回調地獄)

如果非同步返回值又依賴另一個非同步返回值,那麼回調的情況還會進一步變複雜,隨著代碼越來越複雜,回調策略是不具有擴展性,維護起來很麻煩.

 // 第一參數為值
 // 第二參數為正確的回調
 // 第三參數為失敗的回調
 function double(value, success, failure) {
     setTimeout(() => {
         try {
             if (typeof value !== 'number') {
                 throw '必須提供數字作為第一個參數'
             }
             success(value * 2)
         } catch (e) {
             failure(e)
         }
     }, 2000)
 }
 const successCallback = (x) => double(x, (y) => { console.log(`success:${y}`); })
 const failureCallback = (e) => console.log(`failure:${e}`);
 double(3, successCallback, failureCallback)//success:12

5.ES6的解決方案

ES6中推出了一個處理非同步的對象:Promise

Promise解決方式:

Promise 對象代表了未來將要發生的事件,用來傳遞非同步操作的消息。
Promise 對象代表一個非同步操作,有三種狀態:

  •    pending: 初始狀態,不是成功或失敗狀態。
  •    fulfilled: 意味著操作成功完成。
  •    rejected: 意味著操作失敗。

Promise是一個對象,它的參數是一個回調函數,這個回調函數裡面有兩個參數:resolve和reject,分別代表成功狀態fulfilled執行的代碼和失敗狀態rejected執行的代碼
需要註意的是,Promise裡面的代碼本來是當作同步代碼執行的,但是一旦遇到非同步代碼,就會掛起為pending,然後才有後面的非同步操作
Promise解決問題:

問題:從後臺獲取商品數量,並計算商品總價格。

// 這裡我用setTimeout模擬非同步請求,使用random函數隨機獲取商品的數量(假設每件商品的單價為50),最後需要輸出總價
new Promise((resolve,reject) => {
    console.log("開始請求數據。。。");
    setTimeout(() => {
        // 這裡,我們考慮伺服器傳來錯誤的數據,比如負的數量,然後進入reject裡面
        let count = parseInt(Math.random() * 10) - 5; // [-5,5)
        if (count >= 0){
            resolve(count);
        }else{
            reject(count);
        }
    }, 1000);
}).then(data => {
    let price = 50;
    console.log(`商品的數量為:${data},總價為:${data * price}`)
}).catch(err => {
    console.log(`錯誤的商品數量:${err}`)
})

註意:需要註意的是,then只能寫在promise對象上,所以如果想寫多個then,則需要在每個then裡面重新編寫一個promise對象,然後return出去,就可以繼續往下寫then了

多個promise

// 多個promise的編寫
new Promise((reslove, reject) => {
    console.log(`開始向後臺獲取商品數量。。。`)
    setTimeout(() => {
        let count = parseInt(Math.random() * 10) - 5;
        if (count >= 0) {
            reslove(count);
        } else {
            reject(count);
        }
    }, 2000);
}).then(res => {
    console.log(`商品數量正確,為:${res}`)
    // 開啟一個新的promise,用於計算商品的總價是否大於99(假設商品單價為50)
    let totalPrice = res * 50;
    let p = new Promise((reslove, reject) => {
        if (totalPrice > 99) {
            reslove(totalPrice);
        } else {
            reject(totalPrice);
        }
    })
    // 如果想繼續往下編寫then的話,這裡必須要返回一個promise對象,因為then必須要學在promise對象後面
    return p;
}).then(res => {
    console.log(`商品的總價超過了99,打9折,打折後的價格為:${res * 0.9}`);
}).catch(err => {
    if (err >= 0) {
        // 如果參數是大於等於0的,說明不是第一個promise的reject
        console.log(`商品的總價沒超過99,不打折,價格為:${err}`)
    } else {
        console.log(`商品的數量不能為負數,${err}`)
    }
})

但是,如果then多了還是不夠優雅,看著不習慣,因為一般我們的閱讀代碼的習慣是一行一行的從嚮往下看,如果可以將非同步代碼寫成這種形式就好了,ES7就實現了這個功能,使用async/await

6.async/await解決方式

async其實可以理解為promise的語法糖,它將promise的編寫形式改寫為人們更容易理解的串列代碼的形式,使得非同步代碼像同步代碼
async 是一個修飾符,被它修飾的函數叫非同步函數,會預設的返回一個 Promise 的 resolve的值。
await也是一個修飾符,它後面修飾的函數必須返回一個promise對象,它將非同步代碼轉換為同步結果,會阻塞線程,執行完成後才能執行後面的代碼,但是必須用在被async修飾的函數裡面

// 使用async/await修改之前的promise
// 獲取商品數量的方法
let getCount = () => {
    return new Promise((resolve, reject) => {
        console.log("開始請求數據。。。");
        setTimeout(() => {
            // 這裡,我們考慮伺服器傳來錯誤的數據,比如負的數量,然後進入reject裡面
            let count = parseInt(Math.random() * 10) - 5; // [-5,5)
            if (count >= 0) {
                resolve(count);
            } else {
                reject(count);
            }
        }, 1000);
    })
}

// 計算出總價的方法
let totalPrice = (count) => {
    console.log("開始計算商品總價。。。");
    let p = new Promise((resolve,reject) => {
        setTimeout(() => {
            let price = count * 50;
            if (price >= 99){
                resolve(price);
            }else{
                reject(price);
            }
        }, 1000);
    })
    return p;
}
let getPrice = async () => {
    try{
        // 使用await執行
        let count = await getCount();
        console.log(`商品的數量為:${count}`);
        let price = await totalPrice(count);
        console.log(`商品的總價是:${price}`);
    }catch(error){
        console.log(`進入reject,error:${error}`)
    }
}
getPrice();

這裡除去上面的兩部非同步請求的方法,可以看到最後面那個async修飾的函數,裡面使用await,非常的易於理解,就像是同步代碼一樣,下一行代碼必須等上一次代碼執行完成才能繼續執行。說的底層一點就是await會阻塞線程,延遲執行await語句後面的語句。


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

-Advertisement-
Play Games
更多相關文章
  • 鏡像下載、功能變數名稱解析、時間同步請點擊 阿裡雲開源鏡像站 最近換了台新電腦,系統是Win11的,因為之前用Win10的時候,基本都是裝上就能用的,Win11裝好了一打開突然就重啟了,還是有一點驚嚇的。 百度搜索問題,找到了的解決辦法大致分為兩個方面: 1.當前使用的電腦是否支持虛擬化 1.1 CPU是 ...
  • 本文先給出“win10找不到無線網路報錯”的通用解決方案,併在方案中介紹本次出現的“Windows無法自動將IP協議堆棧綁定到網路適配器”問題。 ...
  • 前言 由於部分企業要求本地部署系統(使用企業伺服器進行部署系統且資料庫也部署在同台伺服器),本地部署系統的伺服器往往達不到我們的雲部署伺服器,速度性能更是有所欠缺,特別是在查詢統計報表的時候,雲上幾秒鐘的速度,本地企業需要幾分鐘以上,所以最近對企業資料庫進行了性能優化,簡單一點其實主要進行查詢緩存優 ...
  • 5月《中國資料庫行業分析報告》已正式發佈,報告通過墨天輪“中國資料庫流行度排行”、國內行業動態、典型產品的介紹,以及全球與國內資料庫行業市場份額等數據情況,對國產資料庫在雲、開源道路上的發展現狀、趨勢進行深入盤點分析,嘗試釐清行業發展的關鍵要素,助力資料庫國產化發展。 ...
  • 一、引言 CTE(Common Table Expression) 公用表達式,它是在單個語句的執行範圍內定義的臨時結果集,只在查詢期間有效。它可以自引用,也可在同一查詢中多次引用,實現了代碼段的重覆利用。 CTE最大的好處是提升T-Sql代碼的可讀性,可以以更加優雅簡潔的方式實現遞歸等複雜的查詢。 ...
  • 本文帶你瞭解蘋果 AppStore 的財年和賬單周期,關於 AppStore 開發者賬單和收入,相信很多開發者不一定有接觸,或者接觸時還是有很多疑問沒有時間來學習。另外,還會有一些財年的詭計問題,比如為什麼阿裡巴巴財年是從4月1號到次年的3月31號呢?蘋果財年為什麼這麼奇怪,本文一一為你解答~ ...
  • 5月12日晚上19點,知識賦能第五期第四節課《OpenHarmony標準系統多媒體子系統之音頻解讀》,在OpenHarmony開發者成長計劃社群內成功舉行。 ...
  • 大家好,我是半夏👴,一個剛剛開始寫文的沙雕程式員.如果喜歡我的文章,可以關註➕ 點贊 👍 加我微信:frontendpicker,一起學習交流前端,成為更優秀的工程師~關註公眾號:搞前端的半夏,瞭解更多前端知識! 點我探索新世界! 原文鏈接 ==>http://sylblog.xin/archi ...
一周排行
    -Advertisement-
    Play Games
  • 分組和樹形結構是不一樣的。 樹形結構是以遞歸形式存在。分組是以鍵值對存在的形式,類似於GroupBy這樣的形式。 舉個例子 ID NAME SEX Class 1 張三 男 1 2 李四 女 2 3 王二 男 1 當以Sex為分組依據時則是 Key Value 男 1 張三 男 1 3 王二 男 1 ...
  • NetCore中將SQLServer資料庫備份為Sql腳本 描述: 最近寫項目收到了一個需求, 就是將SQL Server資料庫備份為Sql腳本, 如果是My Sql之類的還好說, 但是在網上搜了一大堆, 全是教你怎麼操作SSMS的, 就很d疼! 解決方案: 通過各種查找資料, 還有一些老哥的幫助, ...
  • 我的Notion Clowd.Squirrel Squirrel.Windows 是一組工具和適用於.Net的庫,用於管理 Desktop Windows 應用程式的安裝和更新。 Squirrel.Windows 對 Windows 應用程式的實現語言沒有任何要求,甚至無需服務端即可完成增量更新。 ...
  • 轉載請註明來源 https://www.cnblogs.com/brucejiao/p/16188865.html 謝謝! 轉載請註明來源 https://www.cnblogs.com/brucejiao/p/16188865.html 謝謝! 轉載請註明來源 https://www.cnblog ...
  • 1. Netty源碼研究筆記(3)——Channel系列 依舊是通過先縱向再橫向的研究方法,在開篇中,我們發現不管是Sever還是Client,最終的啟動是通過調用channel的對應方法來完成的,而這個動作實際在channel綁定的eventLoop中執行。 接下來,我們繼續EchoSever、E ...
  • 大家好,今天給大家介紹一款輕量、快速、穩定可編排的組件式規則引擎框架LiteFlow。 一、LiteFlow的介紹 LiteFlow官方網站和代碼倉庫地址 官方網站:https://yomahub.com/liteflow Gitee托管倉庫:https://gitee.com/dromara/li ...
  • 我使用Spring AOP實現了用戶操作日誌功能 今天答辯完了,復盤了一下系統,發現還是有一些東西值得拿出來和大家分享一下。 需求分析 系統需要對用戶的操作進行記錄,方便未來溯源 首先想到的就是在每個方法中,去實現記錄的邏輯,但是這樣做肯定是不現實的,首先工作量大,其次違背了軟體工程設計原則(開閉原 ...
  • 《零基礎學Java》 繪製幾何圖形 Java可以分別使用 Graphics 和 Graphics2D 繪製圖形,Graphics類 使用不同的方法繪製不同的圖形(drawLine()方法可f以繪製線、drawRect()方法用於繪製矩形、drawOval()方法用於繪製橢圓形)。 Graphics類 ...
  • 本期教程人臉識別第三方平臺為虹軟科技,本文章講解的是人臉識別RGB活體追蹤技術,免費的功能很多可以自行搭配,希望在你看完本章課程有所收穫。 ...
  • 很多人都喜歡使用黑色的主題樣式,包括我自己,使用了差不多三年的黑色主題,但是個人覺得在進行視窗轉換的時候很廢眼睛。 比如IDEA是全黑的,然後需要看PDF或者WORD又變成白色的了,這樣來回切換導致眼睛很累,畢竟現在網頁以及大部分軟體的界面都是白色的。那麼還是老老實實的使用原來比較順眼的模式吧。 1 ...