NodeJS同步和異步實例代碼講述異步編程
NodeJS最大的賣點——事件機(jī)制和異步IO。開發(fā)者需要按異步方式編寫代碼才用得上這個賣點。但不管怎樣,異步編程確實是NodeJS最大的特點,沒有掌握異步編程就不能說是真正學(xué)會了NodeJS。
異步編程的直接體現(xiàn)就是回調(diào)。異步編程依托于回調(diào)來實現(xiàn),但不能說使用了回調(diào)后程序就異步化了。異步編程有很多特有的代碼設(shè)計模式,為了實現(xiàn)同樣的功能,使用同步方式和異步方式編寫的代碼會有很大差異。,本文通過幾個同步和異步實例代碼講述異步編程的使用方法。
函數(shù)返回值
使用一個函數(shù)的輸出作為另一個函數(shù)的輸入是很常見的需求,在同步方式下一般按以下方式編寫代碼:
var output = fn1(fn2('input'));
// Do something.
而在異步方式下,由于函數(shù)執(zhí)行結(jié)果不是通過返回值,而是通過回調(diào)函數(shù)傳遞,因此一般按以下方式編寫代碼:
fn2('input', function (output2) {
fn1(output2, function (output1) {
// Do something.
});
});
可以看到,這種方式就是一個回調(diào)函數(shù)套一個回調(diào)函多,套得太多了很容易寫出>形狀的代碼。
遍歷數(shù)組
在遍歷數(shù)組時,使用某個函數(shù)依次對數(shù)據(jù)成員做一些處理也是常見的需求。如果函數(shù)是同步執(zhí)行的,一般就會寫出以下代碼:
var len = arr.length,
i = 0;
for (; i < len; ++i) {
arr[i] = sync(arr[i]);
}
// All array items have processed.
如果函數(shù)是異步執(zhí)行的,以上代碼就無法保證循環(huán)結(jié)束后所有數(shù)組成員都處理完畢了。如果數(shù)組成員必須一個接一個串行處理,則一般按照以下方式編寫異步代碼:
(function next(i, len, callback) {
if (i < len) {
async(arr[i], function (value) {
arr[i] = value;
next(i + 1, len, callback);
});
} else {
callback();
}
}(0, arr.length, function () {
// All array items have processed.
}));
可以看到,以上代碼在異步函數(shù)執(zhí)行一次并返回執(zhí)行結(jié)果后才傳入下一個數(shù)組成員并開始下一輪執(zhí)行,直到所有數(shù)組成員處理完畢后,通過回調(diào)的方式觸發(fā)后續(xù)代碼的執(zhí)行。
如果數(shù)組成員可以并行處理,但后續(xù)代碼仍然需要所有數(shù)組成員處理完畢后才能執(zhí)行的話,則異步代碼會調(diào)整成以下形式:
(function (i, len, count, callback) {
for (; i < len; ++i) {
(function (i) {
async(arr[i], function (value) {
arr[i] = value;
if (++count === len) {
callback();
}
});
}(i));
}
}(0, arr.length, 0, function () {
// All array items have processed.
}));
可以看到,與異步串行遍歷的版本相比,以上代碼并行處理所有數(shù)組成員,并通過計數(shù)器變量來判斷什么時候所有數(shù)組成員都處理完畢了。
異常處理
JS自身提供的異常捕獲和處理機(jī)制——try..catch..,只能用于同步執(zhí)行的代碼。以下是一個例子。
function sync(fn) {
return fn();
}
try {
sync(null);
// Do something.
} catch (err) {
console.log('Error: %s', err.message);
}
-- Console ------------------------------
Error: object is not a function
可以看到,異常會沿著代碼執(zhí)行路徑一直冒泡,直到遇到第一個try語句時被捕獲住。但由于異步函數(shù)會打斷代碼執(zhí)行路徑,異步函數(shù)執(zhí)行過程中以及執(zhí)行之后產(chǎn)生的異常冒泡到執(zhí)行路徑被打斷的位置時,如果一直沒有遇到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:4
callback(fn());
^
TypeError: object is not a function
at null._onTimeout (/home/user/test.js:4:13)
at Timer.listOnTimeout [as ontimeout] (timers.js:110:15)
因為代碼執(zhí)行路徑被打斷了,我們就需要在異常冒泡到斷點之前用try語句把異常捕獲住,并通過回調(diào)函數(shù)傳遞被捕獲的異常。于是我們可以像下邊這樣改造上邊的例子。
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都按照以上方式設(shè)計,回調(diào)函數(shù)中第一個參數(shù)都是err。因此我們在編寫自己的異步函數(shù)時,也可以按照這種方式來處理異常,與NodeJS的設(shè)計風(fēng)格保持一致。
有了異常處理方式后,我們接著可以想一想一般我們是怎么寫代碼的?;旧?,我們的代碼都是做一些事情,然后調(diào)用一個函數(shù),然后再做一些事情,然后再調(diào)用一個函數(shù),如此循環(huán)。如果我們寫的是同步代碼,只需要在代碼入口點寫一個try語句就能捕獲所有冒泡上來的異常,示例如下。
function main() {
// Do something.
syncA();
// Do something.
syncB();
// Do something.
syncC();
}
try {
main();
} catch (err) {
// Deal with exception.
}
但是,如果我們寫的是異步代碼,就只有呵呵了。由于每次異步函數(shù)調(diào)用都會打斷代碼執(zhí)行路徑,只能通過回調(diào)函數(shù)來傳遞異常,于是我們就需要在每個回調(diào)函數(shù)里判斷是否有異常發(fā)生,于是只用三次異步函數(shù)調(diào)用,就會產(chǎn)生下邊這種代碼。
function main(callback) {
// Do something.
asyncA(function (err, data) {
if (err) {
callback(err);
} else {
// Do something
asyncB(function (err, data) {
if (err) {
callback(err);
} else {
// Do something
asyncC(function (err, data) {
if (err) {
callback(err);
} else {
// Do something
callback(null);
}
});
}
});
}
});
}
main(function (err) {
if (err) {
// Deal with exception.
}
});
可以看到,回調(diào)函數(shù)已經(jīng)讓代碼變得復(fù)雜了,而異步方式下對異常的處理更加劇了代碼的復(fù)雜度。如果NodeJS的最大賣點最后變成這個樣子,那就沒人愿意用NodeJS了。
總結(jié)
不掌握異步編程就不算學(xué)會NodeJS。異步編程下的函數(shù)間數(shù)據(jù)傳遞、數(shù)組遍歷和異常處理與同步編程有很大差別。
到此這篇關(guān)于NodeJS同步和異步實例代碼講述異步編程的文章就介紹到這了,更多相關(guān)NodeJS異步編程的使用實例內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
express搭建的nodejs項目使用webpack進(jìn)行壓縮打包
對于打包這個問題它并不是難點,但是對于我們這種初學(xué)者來說,根本就不知道應(yīng)該怎么做,下面這篇文章主要給大家介紹了關(guān)于express搭建的nodejs項目使用webpack進(jìn)行壓縮打包的相關(guān)資料,需要的朋友可以參考下2022-12-12
pnpm workspace管理monorepo項目使用過程詳解
這篇文章主要為大家介紹了pnpm workspace管理monorepo項目使用過程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-10-10
關(guān)于Node.js的events.EventEmitter用法介紹
本篇文章主要介紹了關(guān)于Node.js的events.EventEmitter用法,具有一定的參考價值,感興趣的小伙伴們可以參考一下。2017-04-04
Node.js中MongoDB更新數(shù)據(jù)的兩種方法
本文主要介紹了Node.js中MongoDB更新數(shù)據(jù)的兩種方法,主要包括使用原生的 MongoDB 驅(qū)動程序或者使用 Mongoose 來更新 MongoDB 數(shù)據(jù),2023-12-12
深入解析桶排序算法及Node.js上JavaScript的代碼實現(xiàn)
桶排序Radix Sort算法利用分治思想將元素分入各桶中排序后匯總,以下我們就來深入解析桶排序算法及Node.js上JavaScript的代碼實現(xiàn),需要的朋友可以參考下2016-07-07
node.js讀取Excel數(shù)據(jù)(下載圖片)的方法示例
這篇文章主要給大家介紹了關(guān)于node.js讀取Excel數(shù)據(jù)(下載圖片)的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用node.js具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-08-08
開箱即用的Node.js+Mysql模塊封裝實現(xiàn)詳解
這篇文章主要為大家介紹了開箱即用的Node.js+Mysql模塊封裝實現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01

