理解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
  • 概述:這個WPF項目通過XAML繪製汽車動態速度表盤,實現了0-300的速度刻度,包括數字、指針,並通過定時器模擬速度變化,展示了動態效果。詳細實現包括界面設計、刻度繪製、指針角度計算等,通過C#代碼與XAML文件結合完成。 新建 WPF 項目: 在 Visual Studio 中創建一個新的 WP ...
  • 概述:在WPF中使用`WpfAnimatedGif`庫展示GIF動畫,首先確保全裝了該庫。通過XAML設置Image控制項,指定GIF路徑,然後在代碼中使用庫提供的方法實現動畫控制。這簡化了在WPF應用中處理GIF圖的過程,提供了方便的介面來管理動畫播放和暫停。 當使用 WpfAnimatedGif  ...
  • 您是否曾經訪問過一個網站,它需要很長時間載入,最終你敲擊 F5 重新載入頁面。 即使用戶刷新了瀏覽器取消了原始請求,而對於伺服器來說,API也不會知道它正在計算的值將在結束時被丟棄,刷新五次,伺服器將觸發 5 個請求。 為瞭解決這個問題,ASP.NET Core 為 Web 伺服器提供了一種機制,就 ...
  • 本章將和大家分享如何通過 Elasticsearch 實現自動補全查詢功能。 一、自動補全-安裝拼音分詞器 1、自動補全需求說明 當用戶在搜索框輸入字元時,我們應該提示出與該字元有關的搜索項,如圖: 2、使用拼音分詞 要實現根據字母做補全,就必須對文檔按照拼音分詞。在 GitHub 上恰好有 Ela ...
  • using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Windows.Forms; namespace OOP { pub ...
  • 概述:以上內容詳細介紹了在C#中如何從另一個線程更新GUI,包括基礎功能和高級功能。對於WinForms,使用`Control.Invoke`;對於WPF,使用`Dispatcher.Invoke`。高級功能使用`SynchronizationContext`實現線程間通信,確保清晰、可讀性高的代碼 ...
  • Nuget包 Microsoft.Extensions.Telemetry.Abstractions 包含的新的日誌記錄source generator,它支持使用[LogProperties]將整個對象作為State與日誌一起記錄。 我將展示一種方法來控制如何使用[LogProperties]對象 ...
  • 支持.Net/.Net Core/.Net Framework,可以部署在Docker, Windows, Linux, Mac。 常見的ORM技術(比如:Entity Framework,Dapper,SqlSugar,NHibernate,等…),它們不是在做Sql語句的程式化變種,就是在做Sq ...
  • 一、引言 在現代應用程式開發中,尤其是在涉及I/O操作(如網路請求、文件讀寫等)時,非同步編程成為了提高性能和用戶體驗的關鍵技術。C#作為.NET框架下的主流開發語言,提供了強大的非同步編程支持,通過async/await關鍵字,可以讓開發者以同步的方式編寫非同步代碼,極大地簡化了非同步編程的複雜性。本文將 ...
  • 一、引言 在.NET開發中,操作Office文檔(特別是Excel和Word)是一項常見的需求。然而,在伺服器端或無Microsoft Office環境的場景下,直接使用Office Interop可能會面臨挑戰。為瞭解決這個問題,開源庫NPOI應運而生,它提供了無需安裝Office即可創建、讀取和 ...