search
尋找貓咪~QQ 地點 桃園市桃園區 Taoyuan , Taoyuan

Node Hero: 3. 理解非同步編程

本章我將指導你學習非同步編程的原理,並向你展示如何在 JavaScript 和 Node.js 中實現非同步編程。

非同步編程

在傳統編程實踐中,大多數 I/O 操作都是同步發生的。如果想想 Java,想想如何用 Java 讀取一個文件,你會得到下面這樣的代碼:

  • try(FileInputStream inputStream = new FileInputStream("foo.txt")) {
  • Session IOUtils;
  • String fileContent = IOUtils.toString(inputStream);
  • }

複製代碼

這背後發生了什麼?主線程會被阻塞,直到文件讀完,這意味著讀文件的同時其它什麼事情都做不了。要解決此問題,更好利用 CPU,就不得不手動管理線程。

如果有更多阻塞操作,那麼事件隊列就變得更糟糕:

(紅色塊表示在進程等待外部資源的響應而被阻塞時,黑色塊表示在代碼運行時,綠色塊表示應用的其餘部分)

為解決這個問題,Node.js 引入了一種非同步編程模型。

Node.js 中的非同步編程

非同步 I/O 是一種輸入/輸出處理的形式,它允許在傳輸完成之前,其它處理能繼續進行。

這篇文章分享之前我還是要推薦下我自己的前端群:657-137-906,不管你是小白還是大牛,小編我都挺歡迎,不定期分享乾貨,包括我自己整理的一份2017最新的前端資料和零基礎入門教程,歡迎初學和進階中的小夥伴。。

在如下的示例中,我將展示 Node.js 中一個簡單的文件讀寫過程 - 同時採用同步和非同步的方式,目的是向你展示通過避免阻塞應用程序,能實現什麼。

下面我們先從一個簡單的示例開始 - 以同步的方式用 Node.js 讀一個文件:

  • const fs = require('fs')
  • let content
  • try {
  • content = fs.readFileSync('file.md', 'utf-8')
  • } catch (ex) {
  • console.log(ex)
  • }
  • console.log(content)

複製代碼

這裡剛剛發生了什麼?我們試圖用 fs 模塊的同步介面讀一個文件。它按預期方式工作 - content 變數會包含 file.md 的內容。這種方式的問題是,Node.js 會被阻塞,直到操作完成 - 也就是說在文件正在被讀取時,它什麼事都做不了。

下面我們看看如何修復!

我們直到,在 JavaScript 中,非同步編程只能用函數這個該語言的一等公民來實現:函數可以像所有其它變數一樣傳給其它函數。 將其它函數作為參數的函數被稱為 高階函數 。

如下是一個高階函數的最簡單示例:

  • const numbers = [2,4,1,5,4]
  • function isBiggerThanTwo (num) {
  • return num > 2
  • }
  • numbers.filter(isBiggerThanTwo)

複製代碼

在上例中,我們將一個函數傳遞給 filter 函數。通過這種方式我們可以定義過濾的邏輯。

這就是回調誕生的方式:如果你把一個函數傳遞給另一個函數作為參數,那麼就可以在另一個函數完成任務時,在該函數內調用傳進來的函數。不需要返回值,只用值調用另一個函數。

這些所謂錯誤優先(error-first)的回調是 Node.js 本身的核心 - 核心模塊用了它,大多數 NPM 中的模塊也是。

  • const fs = require('fs')
  • fs.readFile('file.md', 'utf-8', function (err, content) {
  • if (err) {
  • return console.log(err)
  • }
  • console.log(content)
  • })

複製代碼

這裡要注意:

錯誤處理 : 必須在回調中檢測錯誤,而不是用 try-catch 塊。 沒有返回值 : 非同步函數不返回值,但是值將被傳遞給回調。

下面我們對這個文件做點修改,看看它實際上是如何工作的:

  • const fs = require('fs')
  • console.log('start reading a file...')
  • fs.readFile('file.md', 'utf-8', function (err, content) {
  • if (err) {
  • console.log('error happened during reading the file')
  • return console.log(err)
  • }
  • console.log(content)
  • })
  • console.log('end of the file')

複製代碼

這段腳本的輸出將是:

  • start reading a file...
  • end of the file
  • error happened during reading the file

複製代碼

正如你所見,一旦我們開始讀文件,執行繼續,應用程序列印出 end of the file 。一旦文件讀取完成,我們的回調就只被調用一次。這怎麼可能呢? 迎接事件循環。

事件循環

事件循環是 Node.js / JavaScript 的核心 - 它負責安排非同步操作。

在深入了解之前,要確保理解什麼是事件驅動的編程。

事件驅動的編程是一種編程範式,在這種範式中程序流程是由事件決定的,比如用戶行為(滑鼠點擊、按鍵)、感測器輸出或者其它程序/線程的消息。

實際上,它意味著應用程序按照事件行事。

並且,我們在第一章已經學過,從開發者的觀點看,Node.js 是單線程的。這意味著不必處理線程和線程同步,Node.js 遠離了這種複雜性。除了你的代碼,所有東西都是并行執行的。

非同步控制流

至此你已經對 JavaScript 中的非同步編程工作機制有了一個基本認識,下面我們來看看幾個如何組織代碼的示例。

Async.js

為避免所謂 回調地獄 ,可以做的一件事情是開始使用 async.js 。

Async.js 幫助組織應用程序結構,讓流程式控制制更容易。

下面我們看一個使用 Async.js 的簡短示例,然後用 Promises 重寫。

如下的代碼片段映射三個文件上的狀態:

  • async.parallel(['file1', 'file2', 'file3'], fs.stat, function (err, results) {
  • // 現在結果是是一個每個文件的狀態數組
  • })

複製代碼Promises

Promise 對象用於延遲及非同步計算。一個 Promise 代表還沒有完成,但是未來會執行的操作。

在實踐中,前面的示例可以重寫為如下:

  • function stats (file) {
  • return new Promise((resolve, reject) => {
  • fs.stat(file, (err, data) => {
  • if (err) {
  • return reject (err)
  • }
  • resolve(data)
  • })
  • })
  • }
  • Promise.all([
  • stats('file1'),
  • stats('file2'),
  • stats('file3')
  • ])
  • .then((data) => console.log(data))
  • .catch((err) => console.log(err))

複製代碼

當然,如果使用一個有 Promise 介面的方法,那麼 Promise 示例的行數也會少很多。



熱門推薦

本文由 yidianzixun 提供 原文連結

寵物協尋 相信 終究能找到回家的路
寫了7763篇文章,獲得2次喜歡
留言回覆
回覆
精彩推薦