切換語言為:簡體

如何優雅的在 JavaScript 中 使用 Event Loop與Promise 控制非同步流程

  • 爱糖宝
  • 2024-07-04
  • 2076
  • 0
  • 0

在現代JavaScript開發中,非同步程式設計已成為不可或缺的一部分,它使我們能夠處理耗時操作,如網路請求、檔案讀寫,而不阻塞主執行緒。在最近的學習中瞭解到了Promise和Event Loop,本文將深入探討他們的工作原理、如何優雅地使用Promise控制非同步流程,以及其在JavaScript事件迴圈機制中的角色。

非同步程式設計的需求

在Web開發或其他涉及I/O操作的場景中,經常遇到需要等待某個操作完成才能繼續執行的情況。傳統的解決方案是使用回撥函式,但隨著業務複雜度上升,回撥函式層層巢狀,形成了所謂的“回撥地獄”,這不僅使得程式碼難以閱讀和維護,還可能引發除錯難題。

如下面程式碼,要求需要執行完a再執行b再執行c執行d(假設每個方法中都存在setTimeout),當巢狀無限多時,簡直就是地獄。

function a(cbB,cbC,abD){
    cbB(cbC,cbD)
}

function b(cb,cbD){
    cb(cbD)
}

function c(cb){
    cb()
}

function d(){

}

a(b,c,d)

Promise的誕生

Promise,正如其名,代表了一個未來的值,它或者成功(resolved)並攜帶結果資料,或者失敗(rejected)並攜帶錯誤資訊。Promise的設計初衷就是爲了解決非同步程式設計中的複雜性問題,提供一種更優雅、鏈式呼叫的方式來組織非同步操作。

Promise的基本使用

透過new Promise((resolve, reject) => {...})建構函式建立,其中resolvereject是兩個函式,分別用於改變Promise的狀態。

我以一個案例來給大家講解一下。當getup()狀態為resolve().then()中的computer()開始執行。

function getup() {
    //pending resloved rejected
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('小帥起床了');
            resolve()
        }, 2000)
    })
}

function computer() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('小帥開啟了電腦');
            resolve()
        }, 1000)
    })
}

function study(params) {
    console.log('小帥開始敲程式碼了');
}

getup().then(() => {
    return computer()
})
.then(() => {
    study()
})

執行結果

如何優雅的在 JavaScript 中 使用 Event Loop與Promise 控制非同步流程

.then方法

是promise原型上的一個函式,x.then函式會在x這個promise例項物件狀態變更為resolved之後才執行內部邏輯,由此藉助這個機制可以將非同步捋成同步。then方法支援鏈式呼叫,因為then預設也會返回一個promise物件,但狀態預設是pending,這就會導致後面的then用不上前面then的狀態,從而繼續往前查詢。我們在then中返回一個promise物件,會覆蓋掉then自帶的返回。

還是以上面程式碼為例,刪除computer前的return。為什麼執行結果是先敲程式碼再開啟電腦呢?

因為第一個then沒有返回值,但是它會預設返回一個Promise,但這個Promise的狀態預設是pending(進行中),第二個then無法使用到第一個then的狀態,那麼第二個then就會繼續往前查詢,發現getup()狀態為resolve,此時它就會立即執行,而第一個then則在2s後執行。

不刪除return時,第一個then返回的是computer()Promise,此時第二個then可以識別到第一個then中的狀態為resolve,所以會等待computer()執行完後再執行

getup().then(() => {
    computer()
})
.then(() => {
    study()
})

執行結果

如何優雅的在 JavaScript 中 使用 Event Loop與Promise 控制非同步流程

.catch方法

用於捕獲Promise鏈中任何環節丟擲的錯誤,保證異常處理的一致性。

如一下程式碼,如果Promise的狀態為reject時,會導致程式報錯,增加.catach()方法捕捉錯誤,就會將錯誤處理不會再導致程式報錯。

function a(){
    return new Promise(function(resloved,reject){
        setTimeout(function(){
            console.log('a is ok');
            // resloved('請求到的資料')
            reject('錯誤')
        },1000)
    })
}
function b() {
    console.log(1);
}

a().then((res)=>{
    console.log(res);
    b()
})
.catch((err)=>{
    console.log(err,'xxxx');
})

執行結果

有catch時

如何優雅的在 JavaScript 中 使用 Event Loop與Promise 控制非同步流程

無catch

如何優雅的在 JavaScript 中 使用 Event Loop與Promise 控制非同步流程

瀏覽器引擎

JavaScript引擎和渲染引擎都是現代瀏覽器的重要組成部分。

  • JavaScript引擎負責解析和執行JavaScript程式碼。它讓網頁具有互動性,處理使用者事件,執行非同步操作,以及操縱網頁文件物件模型(DOM)等。知名的JavaScript引擎例如Google Chrome的V8、Mozilla Firefox的SpiderMonkey、Apple Safari的JavaScriptCore(也被稱作Nitro或SquirrelFish)等。

  • 渲染引擎(又稱為佈局引擎或呈現引擎)則負責解析HTML和CSS,構建網頁的視覺化表示,並對其進行佈局和繪製。它確保網頁內容按照CSS樣式和網頁標準進行正確顯示。常見的渲染引擎有Blink(用於Chrome和Opera)、Gecko(用於Firefox)、WebKit(用於Safari)以及歷史上Internet Explorer的Trident(也稱MSHTML)和Microsoft Edge早期版本的EdgeHTML。

事件迴圈(Event Loop)

事件迴圈是JavaScript處理非同步操作的核心機制,確保了即使在單執行緒環境下也能高效地管理任務執行順序。

任務型別

  • 宏任務(Macro Task) :包括setTimeoutsetIntervalsetImmediate(Node.js環境)、I/O操作、UI渲染等,它們在當前執行棧完成後執行。

  • 微任務(Micro Task) :如Promise的回撥、process.nextTick(Node.js)、MutationObserver等,它們在當前執行棧的末尾執行,優先順序高於宏任務。

執行流程

  1. 初始化階段:程式開始執行時,呼叫棧是空的,但底部隱含了全域性執行上下文。同時,微任務佇列和宏任務佇列也是初始狀態,宏任務佇列中通常有一個代表整個指令碼的宏任務。

  2. 執行指令碼:全域性執行上下文被推入呼叫棧,開始同步執行指令碼中的程式碼。執行過程中,任何產生的宏任務或微任務會被分別放入對應的佇列中。

  3. 指令碼執行完畢:噹噹前宏任務(通常是整個指令碼)執行結束,呼叫棧清空至全域性上下文,此時檢查微任務佇列。

  4. 執行微任務:如果微任務佇列不為空,則從隊首開始執行微任務,直至佇列為空,期間新產生的微任務也會立即執行。此過程會持續進行,直到沒有更多的微任務加入佇列。

  5. 宏任務執行:微任務全部執行完畢後,從宏任務佇列中取出下一個任務(如果有),將其推入呼叫棧執行。執行過程中,可能繼續產生新的宏任務和微任務。

  6. 渲染操作:在每次宏任務執行後,如果事件迴圈檢測到渲染時機(即呼叫棧為空且沒有待執行的微任務),渲染引擎會嘗試更新介面。

  7. 迴圈繼續:事件迴圈不斷重複上述過程,檢查佇列,執行任務,直到所有任務(包括宏任務和微任務)都被處理完。

透過這樣的機制,JavaScript能夠高效地管理同步和非同步程式碼,確保在單執行緒環境中既能夠執行復雜的非同步操作,又不阻塞UI渲染或其他任務的執行。

結語

Event Loop機制支撐起了JavaScript的非同步執行模型,而Promise作為一種高階的非同步程式設計抽象,利用事件迴圈的特性,提供了清晰、靈活的非同步解決方案,二者共同構成了現代JavaScript非同步程式設計的核心基礎。

0則評論

您的電子郵件等資訊不會被公開,以下所有項目均必填

OK! You can skip this field.