理解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
  • 移動開發(一):使用.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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...