(五)nodejs循序漸進-回調函數和異常處理(基礎篇)

上篇文章我們講完了類和對象,接下來我們將要說回調函數.

我在第一篇說到nodejs的一個優勢是異步IO,實際上異步IO直接體現就是使用回調函數,當然不是用了回調函數,他就一定是異步IO的,因為inodejs是一個單線程函數,主線程在執行的時候,只有發生了異步處理(文件讀寫、網絡請求、定時任務、讀取數據庫等),js讓操作系統相關部件去處理這些請求,另一方面,它會繼續執行后面的代碼,這才是異步。

回調函數在完成任務后就會被調用,很多Node項目使用了大量的回調函數,Node 所有 API 都支持回調函數。

例如,我們可以一邊處理某一個復雜邏輯運算,一邊執行其他命令,在復雜邏輯運算完成后,我們將運算結果作為回調函數的參數返回。這樣在執行代碼時就沒有阻塞或等待IO操作。這就大大提高了 Node.js 的性能,可以處理大量的并發請求。

回調函數一般作為函數的最后一個參數出現:

function fun1(param1, param2, callback) { }
function fun2(param, callback1, callback2) { }

阻塞IO代碼

代碼如下:

var fs=require("fs");
//demo.txt文件內容是 hello world
var data=fs.readFileSync("demo.txt");
console.log(data.toString());
console.log("讀文件結束");
var a = 12;
console.log("執行其他操作結束");

以上代碼執行結果如下:

hello world
讀文件結束
執行其他操作結束

非阻塞IO代碼

我們把剛才的代碼做個改動

const fs = require('fs')
//demo.txt文件內容是 hello world
fs.readFile('demo.txt', 'utf8', function(err, data){console.log(data);console.log("讀文件結束");
});
var a = 12;
console.log("執行其他操作結束");

?以上代碼執行結果如下:

執行其他操作結束
hello world
讀文件結束

?

以上兩個實例我們了解了阻塞與非阻塞調用的不同。第一個實例在文件讀取完后才執行程序。 第二個實例我們不需要等待文件讀取完,這樣就可以在讀取文件時同時執行接下來的代碼,大大提高了程序的性能。

因此,阻塞是按順序執行的,而非阻塞是不需要按順序的,所以如果需要處理回調函數的參數,我們就需要寫在回調函數內。

異常處理

JS 自身提供的異常捕獲和處理機制—try catch,只能用于同步執行的代碼。以下是一個例子。

try {var b = a /0;
} catch (err) {console.log('Error: %s', err.message);
}

輸出結果為:

Error: a is not defined

可以看到,異常會沿著代碼執行路徑一直順序執行,直到遇到第一個 try 語句時被捕獲住。但由于異步函數會打斷代碼執行路徑,異步函數執行過程中以及執行之后產生的異常冒泡到執行路徑被打斷的位置時,如果一直沒有遇到 try 語句,就作為一個全局異常拋出。以下是一個例子。

function async(fn, callback) {// Code execution path breaks here.setTimeout(function () {callback(fn());}, 0);
}try {async(null, function (data) {// Do something.});
} catch (err) {console.log('Error: %s', err.message);
}-- Console ------------------------------
/home/user/test.js:4callback(fn());^
TypeError: object is not a functionat null._onTimeout (/home/user/test.js:4:13)at Timer.listOnTimeout [as ontimeout] (timers.js:110:15)

因為代碼執行路徑被打斷了,我們就需要在異常冒泡到斷點之前用 try 語句把異常捕獲住,并通過回調函數傳遞被捕獲的異常。于是我們可以像下邊這樣改造上邊的例子。

function async(fn, callback) {// Code execution path breaks here.setTimeout(function () {try {callback(null, fn());} catch (err) {callback(err);}}, 0);
}async(null, function (err, data) {if (err) {console.log('Error: %s', err.message);} else {// Do something.}
});-- Console ------------------------------
Error: object is not a function

可以看到,異常再次被捕獲住了。在 NodeJS 中,幾乎所有異步 API 都按照以上方式設計,回調函數中第一個參數都是 err。因此我們在編寫自己的異步函數時,也可以按照這種方式來處理異常,與 NodeJS 的設計風格保持一致。

有了異常處理方式后,我們接著可以想一想一般我們是怎么寫代碼的。基本上,我們的代碼都是做一些事情,然后調用一個函數,然后再做一些事情,然后再調用一個函數,如此循環。如果我們寫的是同步代碼,只需要在代碼入口點寫一個 try 語句就能捕獲所有冒泡上來的異常,示例如下。

function main() {// Do something.syncA();// Do something.syncB();// Do something.syncC();
}try {main();
} catch (err) {// Deal with exception.
}

但是,如果我們寫的是異步代碼,就只有呵呵了。由于每次異步函數調用都會打斷代碼執行路徑,只能通過回調函數來傳遞異常,于是我們就需要在每個回調函數里判斷是否有異常發生,于是只用三次異步函數調用,就會產生下邊這種代碼。

function main(callback) {// Do something.asyncA(function (err, data) {if (err) {callback(err);} else {// Do somethingasyncB(function (err, data) {if (err) {callback(err);} else {// Do somethingasyncC(function (err, data) {if (err) {callback(err);} else {// Do somethingcallback(null);}});}});}});
}main(function (err) {if (err) {// Deal with exception.}
});

可以看到,回調函數已經讓代碼變得復雜了,而異步方式下對異常的處理更加劇了代碼的復雜度。如果 NodeJS 的最大賣點最后變成這個樣子,那就沒人愿意用 NodeJS 了。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/444547.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/444547.shtml
英文地址,請注明出處:http://en.pswp.cn/news/444547.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

(六)nodejs循序漸進-數據流和文件操作(基礎篇)

Buffer JS 語言自身只有字符串數據類型,沒有二進制數據類型,因此 NodeJS 提供了一個與 String 對等的全局構造函數 Buffer 來提供對二進制數據的操作。除了可以讀取文件得到 Buffer 的實例外,還能夠直接構造,Buffer 與字符串類似…

leetcode171. Excel表列序號

給定一個Excel表格中的列名稱,返回其相應的列序號。 例如, A -> 1 B -> 2 C -> 3 ... Z -> 26 AA -> 27 AB -> 28 ... 示例 1: 輸入: "A" 輸出: 1 示例 2: 輸入: "AB" 輸出: 28 …

(七)nodejs循序漸進-模塊系統(進階篇)

模塊系統 為了讓Node.js的文件可以相互調用,Node.js提供了一個簡單的模塊系統。 模塊是Node.js 應用程序的基本組成部分,文件和模塊是一一對應的。換言之,一個 Node.js 文件就是一個模塊,這個文件可能是JavaScript 代碼、JSON 或…

(八)nodejs循序漸進-事件驅動(進階篇)

事件驅動程序 Node.js 使用事件驅動模型,當web server接收到請求,就把它關閉然后進行處理,然后去服務下一個web請求。 當這個請求完成,它被放回處理隊列,當到達隊列開頭,這個結果被返回給用戶。 這個模型…

leetcode304. 二維區域和檢索 - 矩陣不可變

給定一個二維矩陣,計算其子矩形范圍內元素的總和,該子矩陣的左上角為 (row1, col1) ,右下角為 (row2, col2)。 上圖子矩陣左上角 (row1, col1) (2, 1) ,右下角(row2, col2) (4, 3),該子矩形內元素的總和為 8。 示例…

(九)nodejs循序漸進-Express框架(進階篇)

Express 框架 Express 是一個簡潔而靈活的 node.js Web應用框架, 提供了一系列強大特性幫助你創建各種 Web 應用,和豐富的 HTTP 工具。 使用 Express 可以快速地搭建一個完整功能的網站。 Express 框架核心特性: 可以設置中間件來響應 HTTP 請求。 定…

leetcode326. 3的冪 如此6的操作你想到了嗎

給定一個整數,寫一個函數來判斷它是否是 3 的冪次方。 示例 1: 輸入: 27 輸出: true 示例 2: 輸入: 0 輸出: false 示例 3: 輸入: 9 輸出: true 示例 4: 輸入: 45 輸出: false 進階: 你能不使用循環或者遞歸來完成本題嗎? 注意最后一句…

(十)nodejs循序漸進-高性能游戲服務器框架pomelo之介紹和安裝篇

目錄 Pomelo 安裝Pomelo 創建demoserver項目 pomelo命令 項目結構說明 pomelo框架 架構 服務器實現 客戶端請求與響應、廣播的抽象介紹 Pomelo pomelo是一個快速、可擴展、Node.js分布式游戲服務器框架,對游戲服務器開發感興趣的同學可以關注關注。 之前…

leetcode344. 反轉字符串 史上最簡單力扣題

編寫一個函數,其作用是將輸入的字符串反轉過來。輸入字符串以字符數組 char[] 的形式給出。 不要給另外的數組分配額外的空間,你必須原地修改輸入數組、使用 O(1) 的額外空間解決這一問題。 你可以假設數組中的所有字符都是 ASCII 碼表中的可打印字符。…

(十一)nodejs循序漸進-高性能游戲服務器框架pomelo之啟動流程和組件

游戲啟動過程 啟動入口 在使用pomelo進行游戲開發時,工程目錄下的app.js是整個游戲服務器的啟動運行入口。app.js中創建項目,進行默認配置并啟動服務器的代碼如下: var pomelo require(pomelo); var app pomelo.createApp(); app.set(na…

(十二)nodejs循序漸進-高性能游戲服務器框架pomelo之創建一個游戲聊天服務器

上個章節我們簡單介紹了下pomelo的安裝和目錄結構,有讀者可能覺得有點吃不消,為什么不再深入講一講目錄結構和里邊的庫,這里我就不費口舌了,大家可以去官網參考文檔說明,本文只告訴大家如何利用這個框架來開發自己的東…

看這玩意復習你還會掛科?《軟件工程篇》

軟件工程:是指導軟件開發和維護的一門工程學科 三要素方法/工具/開發過程 價值:促進項目成功 現代產品開發三原則:功用性、可行性、稱許性 軟件過程是軟件工程的核心組成部分。 迭代 :反復求精 增量:逐塊建造 需…

C++:02---命名空間

一、概念: ①類似于倉庫,空間內存儲代碼,需要用到時調用②也為防止名字沖突提供了更加可控的機制二、命名空間的定義 定義的基本格式如下:namespace 命名空間名 { //一系列聲明與定義 };三、命名空間的注意事項 命名空間定義時最后的分號可有可無只要出現在全局作用域中的…

看這玩意復習你還會掛科?《軟件工程2篇》

第一章: 軟件工程定義: 1968年10月,Fritz Bauer 首次提出了“軟件工程”的概念,并將“軟件工程”定義為:為了經濟地獲得能夠在實際機器上有效運行的可靠軟件,而建立并使用的一系列工程化原則。 1993年IE…

C++:05---命名空間

一、概念: ①類似于倉庫,空間內存儲代碼,需要用到時調用②也為防止名字沖突提供了更加可控的機制二、命名空間的定義 定義的基本格式如下:namespace 命名空間名 { //一系列聲明與定義 };三、命名空間的注意事項 命名空間定義時最后的分號可有可無只要出現在全局作用域中的…

C++:04---內聯函數

1.概念: 內聯類似于宏定義,當程序執行到內聯函數時,相當于復制了一份函數代碼。犧牲代碼空間,贏得了時間 內聯說明只是向編譯器發出一個請求,編譯器可以選擇忽略這個請求 2.關鍵字:inline 聲明時寫了inline,定義時可省略。建議聲明和定義都加上inlineinline int add(int…

leetcode86. 分隔鏈表

給定一個鏈表和一個特定值 x,對鏈表進行分隔,使得所有小于 x 的節點都在大于或等于 x 的節點之前。 你應當保留兩個分區中每個節點的初始相對位置。 示例: 輸入: head 1->4->3->2->5->2, x 3 輸出: 1->2->2->4->3->5…

(十三)nodejs循序漸進-高性能游戲服務器框架pomelo之擴展聊天服務器為機器人自動聊天

聊天服務器擴展 大家在上一篇文章里相信已經學會了pomelo框架的基本用法了,那么我們在上一篇文章的代碼基礎上繼續擴展,豐富系統,另外也熟悉下他的更多的用法,這一節我將擴展它:增加一個機器人自動聊天的功能。 目的…

C++:09---類靜態成員、類常量成員

一、類靜態成員(static) 先介紹一下什么是靜態變量、靜態函數 靜態局部變量:存在域(全局數據區),作用域(塊作用域)靜態全局變量:存在域(全局數據區),作用域(整個文件)靜態函數:存在域(全局數據區),作用域(整個文件)static int a=10;//全局靜態變量 static vo…

C++:08---成員變量初始化方式

成員變量初始化有三種方式: 在構造函數體內賦值初始化在自定義的公有函數體中賦值初始化(一般用于成員變量的初始化)在構造函數的成員初始化列表初始化一、構造函數體內初始化 說明:在構造函數體內的初始化方式,本質是是為成員變量賦值,而不是真正意義上的初始化,這點要…