搜尋
首頁web前端js教程總結分享了解nodejs的幾個關鍵節點

總結分享了解nodejs的幾個關鍵節點

Jul 06, 2022 pm 08:36 PM
nodejsnodejs​

總結分享了解nodejs的幾個關鍵節點

本文是個人在實際開發和學習中對nodejs的一些理解,現整理出來方便日後查閱,如果能給您啟發將不勝榮幸。

非阻塞I/O

I/O:即 Input / Output,一個系統的輸入與輸出。

一個系統可以理解為一個個體,比如說一個人,你說話就是輸出,你聽就是輸入。

阻塞 I/O 與非阻塞 I/O 的差異就在於系統接收輸入再到輸出期間,能不能接收其他輸入

下面以兩個例子來說明什麼是阻塞I/O 和非阻塞I/O:

#1、打飯

總結分享了解nodejs的幾個關鍵節點

首先我們要確定一個系統的範圍,在這個例子中食堂阿姨和餐廳的服務生看成是一個系統,輸入就是點菜,輸出就是端菜

那麼在點菜和端菜之間能不能接受其他人的點菜,就可以判斷是阻塞I/O還是非阻塞I/O。

對於食堂阿姨,他在點菜的時候,是不能幫其他同學點菜的,只有這個同學點完菜端菜走了之後,才能接受下一個同學的點菜,所以食堂阿姨是阻塞I/O。

對於餐廳服務員,他可以在點完菜以後,這個客人端菜之前是可以服務下一位客人的,所以服務員是非阻塞I/O。

2、做家事

總結分享了解nodejs的幾個關鍵節點

#在洗衣服的時候,是不需要等著洗衣機旁邊的,這個時候可以去掃地和整理書桌,當整理完書桌後衣服也洗好了,這個時候去晾衣服,那麼總共只需要25分鐘。

洗衣服其實就是一個非阻塞I/O,在把衣服丟進洗衣機和洗完衣服期間,你是可以乾其他事情的。

非阻塞I/O之所以能提升效能,是因為它可以把不必要的等待給節省掉。

理解非阻塞I/O的要點在於

  • #確定一個進行I/O的系統邊界。這非常關鍵,如果把系統擴大,上面餐廳的例子,如果把系統擴大到整個餐廳,那麼廚師肯定是一個阻塞 I/O。
  • 在 I/O 過程中,能不能進行其他 I/O。

nodejs的非阻塞 I/O

nodejs的非阻塞 I/O 是怎麼體現的呢?前面說過要理解非阻塞 I/O 的一個重要點是先確定一個系統邊界,node的系統邊界就是主執行緒

如果下面的架構圖按照線程的維護劃分,左邊虛線部分是nodejs線程,右邊虛線部分是c 線程。

總結分享了解nodejs的幾個關鍵節點

現在nodejs 執行緒需要去查詢資料庫,這是一個典型的I/O 操作,它不會等待I/O 的結果,而且繼續處理其他的操作,它會把大量的運算能力分送到其他的c 線程去計算。

等到結果出來後回到nodejs線程,在獲得結果之前nodejs 線程還能進行其他的I/O操作,所以是非阻塞的。

nodejs 線程 相當於左邊部分是服務員,c 線程是廚師。

所以,node的非阻塞I/O是透過呼叫c 的worker threads來完成的。

那當 c 執行緒取得結果後要怎麼通知 nodejs 執行緒呢?答案是事件驅動

事件驅動

阻塞:I/O時進程休眠,等待I/O完成後進行下一步; 

#非阻塞:I/O時函數立即傳回,進程不等待I/O完成。

那怎麼知道回傳的結果,就需要用到事件驅動

所謂事件驅動可以理解為跟前端點擊事件一樣,我先寫一個點擊事件,但是我不知道什麼時候觸發,只有觸發的時候就去讓主線程執行事件驅動函數。

這種模式也是一種觀察者模式,就是我先監聽這個事件,等觸發時我就去執行。

那要怎麼實現事件驅動呢?答案是非同步程式設計

非同步程式設計

上面說過nodejs有大量的非阻塞I/O,那麼非阻塞I/O的結果是需要透過回呼函數來取得的, 這種透過回呼函數的方式,就是非同步程式設計。例如下面的程式碼是透過回呼函數取得結果的:

glob(__dirname+'/**/*', (err, res) => {
    result = res
    console.log('get result')
})

回呼函數格式規格

nodejs的回呼函數第一個參數是error,後面的參數才是結果。為什麼要這麼做呢?

try {
  interview(function () {
       console.log('smile')
  })
} catch(err) {
    console.log('cry', err)
}

function interview(callback) {
    setTimeout(() => {
        if(Math.random() <p>執行之後,沒有被捕獲,錯誤被丟到了全局,導致整個nodejs程式崩潰了。 </p><p><img src="/static/imghwm/default1.png" data-src="https://img.php.cn/upload/image/244/886/980/1657110712466688.png?x-oss-process=image/resize,p_40" class="lazy" title="1657110712466688.png" alt="總結分享了解nodejs的幾個關鍵節點"></p><p>沒有被try catch捕捉是因為setTimeout重新開啟了事件循環,每開啟一個事件循環就重新生一個呼叫棧context,try catch是屬於上一個事件循環的呼叫堆疊的,setTimeout的回呼函數執行的時候,呼叫堆疊都不一樣了,在這個新的呼叫堆疊中是沒有try catch,所以這個錯誤被丟到全局,無法捕捉。具體可以參考這篇文章<a href="https://juejin.cn/post/6995749646366670855" target="_blank" title="https://juejin.cn/post/6995749646366670855">非同步佇列進行try catch時的問題</a>。 </p><p>那麼怎麼辦呢?把錯誤也當作一個參數:</p><pre class="brush:php;toolbar:false">function interview(callback) {
    setTimeout(() => {
        if(Math.random() <p>但是這樣就比較麻煩,在回呼中還要判斷,所以就產生一種約定成熟的規定,第一個參數是err,如果不存在表示執行成功。 </p><pre class="brush:php;toolbar:false">function interview(callback) {
    setTimeout(() => {
        if(Math.random() <h3 id="strong-非同步流程控制-strong"><strong>非同步流程控制</strong></h3><p>nodejs的回呼寫法,不僅會帶來回呼地域,還會帶來<strong>非同步流程控制</strong>的問題。 </p><p>非同步流程控制主要是指當並發的時候,怎麼來處理並發的邏輯。還是上面的例子,如果你同事面試兩家公司,只有成功面試兩家的時候,才可以不面試第三家,那怎麼寫這個邏輯呢?需要全域頂一個變數count:</p><pre class="brush:php;toolbar:false">var count = 0
interview((err) => {
    if (err) {
        return
    }
    count++
    if (count >= 2) {
        // 处理逻辑
    }
})

interview((err) => {
    if (err) {
        return
    }
    count++
    if (count >= 2) {
        // 处理逻辑
    }
})

像上面這種寫法就非常麻煩,而且難看。所以,後來就出現了promise,async/await的寫法。

promise

目前事件循環得不到的結果,但未來的事件循環會給你結果。很像渣男說的話。

promise不只是渣男,還是狀態機:

  • pending
  • fulfilled/resolved
  • rejectd
const pro = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('2')
    }, 200)
})
console.log(pro) // 打印:Promise { <pending> }</pending>

then & .catch

  • resolved 狀態的promise 會呼叫後面的第一個then
  • rejected 狀態的promise 會呼叫後面的第一個catch
  • 任何一個reject 狀態且後面沒有.catch 的promise,都會造成瀏覽器或node 環境的全域錯誤。 uncaught 表示未捕獲的錯誤。

總結分享了解nodejs的幾個關鍵節點

執行then或catch會傳回一個新的promise,該promise最終狀態根據then和catch的回呼函數的執行結果決定:

  • 如果回呼函數總是throw new Error,該promise是rejected狀態
  • 如果回呼函數始終是return,則該promise是resolved狀態
  • 但如果回呼函數總是return一個promise,該promise會和回呼函數return的promise狀態保持一致
function interview() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (Math.random() > 0.5) {
                resolve('success')
            } else {
                reject(new Error('fail'))
            }
        })
    })
}

var promise = interview()
var promise1 = promise.then(() => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('accept')
        }, 400)
    })
})

promise1的狀態是由return裡面的promise的狀態決定的,也就是return裡面的promise執行完後的狀態就是promise1的狀態。這樣有什麼好處呢?這樣可以解決回呼地獄的問題

var promise = interview()
    .then(() => {
        return interview()
    })
    .then(() => {
        return interview()
    })
    .then(() => {
        return interview()
    })
    .catch(e => {
        console.log(e)
    })

then如果回傳的promise的狀態是rejected,那麼會呼叫後面第一個catch,後面的then就不會在呼叫了。記住:rejected呼叫後面的第一個catch,resolved呼叫後面的第一個then。

promise解決非同步流程控制

如果promise只是為了解決地獄回調,太小看promise了,promise最主要的作用是解決非同步流程控制問題。下面如果要同時面試兩家公司:

function interview() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (Math.random() > 0.5) {
                resolve('success')
            } else {
                reject(new Error('fail'))
            }
        })
    })
}

promise
    .all([interview(), interview()])
    .then(() => {
        console.log('smile')
    })
    // 如果有一家公司rejected,就catch
    .catch(() => {
        console.log('cry')
    })

async/await

#sync/await到底是什麼:

console.log(async function() {
    return 4
})

console.log(function() {
    return new Promise((resolve, reject) => {
        resolve(4)
    })
})

列印的結果一樣,也就是async/await是promse的語法糖而已。

我們知道try catch捕獲錯誤是依賴呼叫堆疊的,只能捕捉到呼叫堆疊以上的錯誤。但如果使用await後能捕捉到呼叫棧所有函數的錯誤。即便這個錯誤是在另一個事件循環的呼叫堆疊中拋出的,例如setTimeout。

改造面試程式碼,可以看到程式碼精簡了很多。

try {
    await interview(1)
    await interview(2)
    await interview(2)
} catch(e => {
    console.log(e)
})

如果是平行任務呢?

await Promise.all([interview(1), interview(2)])

事件循環

因為nodejs的非阻塞I/0, 所以需要利用事件驅動的方式來取得I/O 的結果,實作事件驅動拿到結果必須使用非同步編程,例如回調函數。那麼如何來有序的執行這些回呼函數來獲取結果呢?那就需要使用事件循環。

事件循環是實作 nodejs 非阻塞 I/O 功能的關鍵基礎,非阻塞I/O和事件循環都是屬於 libuv 這個c 函式庫提供的能力。

總結分享了解nodejs的幾個關鍵節點

代码演示:

const eventloop = {
    queue: [],
    loop() {
        while(this.queue.length) {
            const callback = this.queue.shift()
            callback()
        }
        setTimeout(this.loop.bind(this), 50)
    },
    add(callback) {
        this.queue.push(callback)
    }
}

eventloop.loop()

setTimeout(() => {
    eventloop.add(() => {
        console.log('1')
    })
}, 500)

setTimeout(() => {
	eventloop.add(() => {
		console.log('2')
	})
}, 800)

setTimeout(this.loop.bind(this), 50)保证了50ms就会去看队列中是否有回调,如果有就去执行。这样就形成了一个事件循环。

当然实际的事件要复杂的多,队列也不止一个,比如有一个文件操作对列,一个时间对列。

const eventloop = {
    queue: [],
    fsQueue: [],
    timerQueue: [],
    loop() {
        while(this.queue.length) {
            const callback = this.queue.shift()
            callback()
        }
        this.fsQueue.forEach(callback => {
            if (done) {
                callback()
            }
        })
        setTimeout(this.loop.bind(this), 50)
    },
    add(callback) {
        this.queue.push(callback)
    }
}

总结

首先我们弄清楚了什么是非阻塞I/O,即遇到I/O立刻跳过执行后面的任务,不会等待I/O的结果。当I/O处理好了之后就会调用我们注册的事件处理函数,这就叫事件驱动。实现事件驱动就必须要用异步编程,异步编程是nodejs中最重要的环节,它从回调函数到promise,最后到async/await(使用同步的方法写异步逻辑)。

更多node相关知识,请访问:nodejs 教程

以上是總結分享了解nodejs的幾個關鍵節點的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文轉載於:掘金社区。如有侵權,請聯絡admin@php.cn刪除
Python vs. JavaScript:學習曲線和易用性Python vs. JavaScript:學習曲線和易用性Apr 16, 2025 am 12:12 AM

Python更適合初學者,學習曲線平緩,語法簡潔;JavaScript適合前端開發,學習曲線較陡,語法靈活。 1.Python語法直觀,適用於數據科學和後端開發。 2.JavaScript靈活,廣泛用於前端和服務器端編程。

Python vs. JavaScript:社區,圖書館和資源Python vs. JavaScript:社區,圖書館和資源Apr 15, 2025 am 12:16 AM

Python和JavaScript在社區、庫和資源方面的對比各有優劣。 1)Python社區友好,適合初學者,但前端開發資源不如JavaScript豐富。 2)Python在數據科學和機器學習庫方面強大,JavaScript則在前端開發庫和框架上更勝一籌。 3)兩者的學習資源都豐富,但Python適合從官方文檔開始,JavaScript則以MDNWebDocs為佳。選擇應基於項目需求和個人興趣。

從C/C到JavaScript:所有工作方式從C/C到JavaScript:所有工作方式Apr 14, 2025 am 12:05 AM

從C/C 轉向JavaScript需要適應動態類型、垃圾回收和異步編程等特點。 1)C/C 是靜態類型語言,需手動管理內存,而JavaScript是動態類型,垃圾回收自動處理。 2)C/C 需編譯成機器碼,JavaScript則為解釋型語言。 3)JavaScript引入閉包、原型鍊和Promise等概念,增強了靈活性和異步編程能力。

JavaScript引擎:比較實施JavaScript引擎:比較實施Apr 13, 2025 am 12:05 AM

不同JavaScript引擎在解析和執行JavaScript代碼時,效果會有所不同,因為每個引擎的實現原理和優化策略各有差異。 1.詞法分析:將源碼轉換為詞法單元。 2.語法分析:生成抽象語法樹。 3.優化和編譯:通過JIT編譯器生成機器碼。 4.執行:運行機器碼。 V8引擎通過即時編譯和隱藏類優化,SpiderMonkey使用類型推斷系統,導致在相同代碼上的性能表現不同。

超越瀏覽器:現實世界中的JavaScript超越瀏覽器:現實世界中的JavaScriptApr 12, 2025 am 12:06 AM

JavaScript在現實世界中的應用包括服務器端編程、移動應用開發和物聯網控制:1.通過Node.js實現服務器端編程,適用於高並發請求處理。 2.通過ReactNative進行移動應用開發,支持跨平台部署。 3.通過Johnny-Five庫用於物聯網設備控制,適用於硬件交互。

使用Next.js(後端集成)構建多租戶SaaS應用程序使用Next.js(後端集成)構建多租戶SaaS應用程序Apr 11, 2025 am 08:23 AM

我使用您的日常技術工具構建了功能性的多租戶SaaS應用程序(一個Edtech應用程序),您可以做同樣的事情。 首先,什麼是多租戶SaaS應用程序? 多租戶SaaS應用程序可讓您從唱歌中為多個客戶提供服務

如何使用Next.js(前端集成)構建多租戶SaaS應用程序如何使用Next.js(前端集成)構建多租戶SaaS應用程序Apr 11, 2025 am 08:22 AM

本文展示了與許可證確保的後端的前端集成,並使用Next.js構建功能性Edtech SaaS應用程序。 前端獲取用戶權限以控制UI的可見性並確保API要求遵守角色庫

JavaScript:探索網絡語言的多功能性JavaScript:探索網絡語言的多功能性Apr 11, 2025 am 12:01 AM

JavaScript是現代Web開發的核心語言,因其多樣性和靈活性而廣泛應用。 1)前端開發:通過DOM操作和現代框架(如React、Vue.js、Angular)構建動態網頁和單頁面應用。 2)服務器端開發:Node.js利用非阻塞I/O模型處理高並發和實時應用。 3)移動和桌面應用開發:通過ReactNative和Electron實現跨平台開發,提高開發效率。

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
4 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
4 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
4 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.聊天命令以及如何使用它們
4 週前By尊渡假赌尊渡假赌尊渡假赌

熱工具

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

強大的PHP整合開發環境

PhpStorm Mac 版本

PhpStorm Mac 版本

最新(2018.2.1 )專業的PHP整合開發工具

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

VSCode Windows 64位元 下載

VSCode Windows 64位元 下載

微軟推出的免費、功能強大的一款IDE編輯器

Dreamweaver Mac版

Dreamweaver Mac版

視覺化網頁開發工具