深入淺析NodeJs并發(fā)異步的回調(diào)處理
這里說并發(fā)異步,并不準(zhǔn)確,應(yīng)該說連續(xù)異步。NodeJs單線程異步的特性,直接導(dǎo)致多個(gè)異步同時(shí)進(jìn)行時(shí),無法確定最后的執(zhí)行結(jié)果來回調(diào)。舉個(gè)簡單的例子:
for(var i = 0; i < 5; i++) { fs.readFile('file', 'utf-8', function(error, data){}); }
連續(xù)發(fā)起了5次讀文件的異步操作,很簡單,那么問題來了,我怎么確定所有異步都執(zhí)行完了呢?因?yàn)橐谒鼈兌紙?zhí)行完后,才能進(jìn)行之后的操作。相信有點(diǎn)經(jīng)驗(yàn)的同學(xué)都會想到使用記數(shù)的方式來進(jìn)行,但如何保證記數(shù)正確又是一個(gè)問題。仔細(xì)想想:
回調(diào)是一個(gè)函數(shù),每個(gè)異步操作時(shí)將計(jì)數(shù)器+1,當(dāng)每個(gè)異步結(jié)束時(shí)將計(jì)數(shù)器-1,通過判斷計(jì)數(shù)器是否為0來確定是否執(zhí)行回調(diào)。這個(gè)邏輯很簡單,需要一個(gè)相對于執(zhí)行時(shí)和回調(diào)時(shí)的全局變量作為計(jì)數(shù)器,而且要在傳給異步方法是執(zhí)行+1的操作,而且之后將返回一個(gè)用來回調(diào)的函數(shù),有點(diǎn)繞,不過看看Js函數(shù)的高級用法:
var pending = (function() { var count = 0; return function() { count++; return function() { count--; if (count === 0) { // 全部執(zhí)行完畢 } } } });
當(dāng)pending調(diào)用時(shí),即pending(),比如:
var done = pending();
這時(shí)計(jì)數(shù)變量count即被初始化為0,返回的函數(shù)附給了done,這時(shí)如果執(zhí)行done(),會是什么?是不是直接執(zhí)行pending返回的第一個(gè)函數(shù),即:pending()(),這個(gè)執(zhí)行又是什么,首先將計(jì)數(shù)變量count+1,又返回了一個(gè)函數(shù),這個(gè)函數(shù)直接當(dāng)做callback傳給異步的方法,當(dāng)執(zhí)行這個(gè)callback的時(shí)候,首先是將計(jì)數(shù)變量count-1,再判斷count是否為0,如果為0即表示所有的異步執(zhí)行完成了,從而達(dá)到連續(xù)的異步,同一回調(diào)的操作。
關(guān)鍵就在兩個(gè)return上,簡單的說:
第一個(gè)return的函數(shù)是將count+1,接著返回需要回調(diào)的函數(shù)
第二個(gè)return的函數(shù)就是需要回調(diào)的函數(shù),如果它執(zhí)行,就是將count-1,然后判斷異步是否全部執(zhí)行完成,完成了,就回調(diào)
看個(gè)實(shí)際點(diǎn)的例子,讀取多個(gè)文件的異步回調(diào):
var fileName = ['1.html', '2.html', '3.html']; var done = pending(function(fileData) { console.log('done'); console.log(fielData); }); for(var i = 0; i < fileName.lenght; i++) { fs.readFile(fileName[i], 'utf-8', done(fileName[i])); }
其中的done,即用pending方法包起了我們想回調(diào)執(zhí)行的方法,當(dāng)計(jì)數(shù)器為0時(shí),就會執(zhí)行它,那我們得改進(jìn)一下pending方法:
var pending = (function(callback) { var count = 0; var returns = {}; console.log(count); return function(key) { count++; console.log(count); return function(error, data) { count--; console.log(count); returns[key] = data; if (count === 0) { callback(returns); } } } });
callback即為我們的回調(diào)函數(shù),當(dāng)var done = pending(callback)時(shí),done其實(shí)已為第一個(gè)return的函數(shù),它有一個(gè)參數(shù),可以當(dāng)做返回的值的下標(biāo),所以在循環(huán)體中done(fileName[i]),把文件名傳了進(jìn)去。這個(gè)done()是直接執(zhí)行的,它將count+1后,返回了要傳給異步方法的回調(diào)函數(shù),如前面所說,這個(gè)回調(diào)函數(shù)里會根據(jù)計(jì)數(shù)變量來判斷是否執(zhí)行我們希望執(zhí)行的回調(diào)函數(shù),而且把文件的內(nèi)容傳給了它,即returns。好了,運(yùn)行一下,相信能夠準(zhǔn)確的看到運(yùn)行結(jié)果。
0
1
2
3
2
1
0
done
{"1.html": "xxx", "2.html": "xxx", "3.html": "xxx"}
從計(jì)數(shù)上明顯能看出,從0-3再到0,之后就是我們的回調(diào)函數(shù)輸出了done和文件的內(nèi)容。
這個(gè)問題解決了,我們要思考一下,如何讓這樣的方法封裝重用,不然,每次都寫pending不是很不科學(xué)嗎?
下面看看UnJs(我的一個(gè)基于NodeJs的Web開發(fā)框架)的處理方式,應(yīng)用于模板解析中的子模板操作:
unjs.asyncSeries = function(task, func, callback) { var taskLen = task.length; if (taskLen <= 0) { return; } var done = unjs.pending(callback); for(var i = 0; i < taskLen; i++) { func(task[i], done); } }
asyncSeries有三個(gè)參數(shù),意思是:
task: 需要處理的對象,比如需要讀取的文件,它是一個(gè)列表,如果不是列表,或列表長度為0,它將不會執(zhí)行
func: 異步方法,比如fs.readFile,就是通過它傳進(jìn)去的
callback: 我們希望回調(diào)的方法
done和前面同理,它傳給了func,但并沒有執(zhí)行,因?yàn)橄M麘?yīng)用端能可控制參數(shù),所以讓應(yīng)用端去執(zhí)行。
再看看處理子模板時(shí)的操作:
var subTemplate = []; var patt = /\{\% include \'(.+)\' \%\}/ig; while(sub = patt.exec(data)) { var subs = sub; subTemplate.push([subs[0], subs[1]]); } unjs.asyncSeries(subTemplate, function(item, callback) { fs.readFile('./template/' + item[1], 'utf-8', callback(item[0])); }, function(data) { for(var key in data) { html = html.replace(key, data[key]); } });
subTemplate這個(gè)列表,是根據(jù)對子模板的解析生成的數(shù)據(jù),它是一個(gè)二維的數(shù)組,每個(gè)子項(xiàng)的第一個(gè)值為子模板的調(diào)用文本,即:{% include 'header.html' %}這樣的字符串,第二個(gè)參數(shù)為子模板文件名,即:header.html
asyncSeries的第二個(gè)參數(shù)是的callback,實(shí)際上是第三個(gè)參數(shù),也就是我們希望執(zhí)行的回調(diào)函數(shù)經(jīng)過pending處理的回調(diào)方法,如前面所說,在asyncSeries內(nèi)部,它并沒有運(yùn)行,而是到這里運(yùn)行的,即:callback(item[0]),帶上了參數(shù),因?yàn)楹竺孢€要根據(jù)這個(gè)參數(shù)將父模板中調(diào)用子模板的字符串替換為對應(yīng)子模板的內(nèi)容。
這樣子,只要需要連續(xù)異步時(shí),就可以使用asyncSeries方法來處理了。因?yàn)楫惒降年P(guān)系,程序的流程有點(diǎn)繞,可能開始不太好理解,即使熟悉了,也有可能突然想不明白,沒關(guān)系,比如,第二個(gè)參數(shù)中的callback實(shí)際是第三個(gè)參數(shù)生成的,開始可能你就會想,這個(gè)callback倒底是啥。還有就是pending的兩個(gè)return,也是不太好理解的,需要多想想。
好了,連續(xù)異步的回調(diào)使用Js函數(shù)的高級特性完成了。但NodeJs的異步性著實(shí)讓程序的控制很成問題,諸如還有連續(xù)異步,但要傳值的操作等,這些都是可以通過這樣的思路,變化一下即可實(shí)現(xiàn)的。
以上內(nèi)容是小編給大家分享的NodeJs并發(fā)異步的回調(diào)處理的相關(guān)知識,希望大家喜歡。
相關(guān)文章
node.js中的http.createServer方法使用說明
這篇文章主要介紹了node.js中的http.createServer方法使用說明,本文介紹了http.createServer的方法說明、語法、接收參數(shù)、使用實(shí)例和實(shí)現(xiàn)源碼,需要的朋友可以參考下2014-12-12使用pkg打包nodejs項(xiàng)目并解決本地文件讀取的問題
這篇文章主要介紹了使用pkg打包nodejs項(xiàng)目并解決本地文件讀取的問題,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-10-10淺談Node.js CVE-2017-14849 漏洞分析(詳細(xì)步驟)
這篇文章主要介紹了淺談Node.js CVE-2017-14849 漏洞分析(詳細(xì)步驟),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-11-11node連接MySQL數(shù)據(jù)庫的3種方式總結(jié)
現(xiàn)在前端基本上都會用一些NodeJs,想必也想自己寫一些API或者個(gè)人博客的后臺系統(tǒng),這些就離不開連接數(shù)據(jù)庫的問題,下面這篇文章主要給大家介紹了關(guān)于node連接MySQL數(shù)據(jù)庫的3種方式,需要的朋友可以參考下2022-08-08在Linux系統(tǒng)中搭建Node.js開發(fā)環(huán)境的簡單步驟講解
這篇文章主要介紹了在Linux系統(tǒng)中搭建Node.js開發(fā)環(huán)境的步驟,Node使得JavaScript程序可以在本地操作系統(tǒng)環(huán)境中解釋運(yùn)行,需要的朋友可以參考下2016-01-01node.js中的fs.truncateSync方法使用說明
這篇文章主要介紹了node.js中的fs.truncateSync方法使用說明,本文介紹了fs.truncateSync的方法說明、語法、接收參數(shù)、使用實(shí)例和實(shí)現(xiàn)源碼,需要的朋友可以參考下2014-12-12