NodeJs併發異步的回調處理

這裏說併發異步,並不許確,應該說連續異步。NodeJs單線程異步的特性,直接致使多個異步同時進行時,沒法肯定最後的執行結果來回調。舉個簡單的例子:html

for(var i = 0; i < 5; i++) {
    fs.readFile('file', 'utf-8', function(error, data){});
}

連續發起了5次讀文件的異步操做,很簡單,那麼問題來了,我怎麼肯定全部異步都執行完了呢?由於要在它們都執行完後,才能進行以後的操做。相信有點經驗的同窗都會想到使用記數的方式來進行,但如何保證記數正確又是一個問題。仔細想一想:數組

回調是一個函數,每一個異步操做時將計數器+1,當每一個異步結束時將計數器-1,經過判斷計數器是否爲0來肯定是否執行回調。這個邏輯很簡單,須要一個相對於執行時和回調時的全局變量做爲計數器,並且要在傳給異步方法是執行+1的操做,並且以後將返回一個用來回調的函數,有點繞,不過看看Js函數的高級用法:併發

var pending = (function() {
    var count = 0;
    return function() {
        count++;
        return function() {
            count--;
            if (count === 0) {
                // 所有執行完畢
            }
        }
    }
});

當pending調用時,即pending(),好比:框架

var done = pending();

這時計數變量count即被初始化爲0,返回的函數附給了done,這時若是執行done(),會是什麼?是否是直接執行pending返回的第一個函數,即:pending()(),這個執行又是什麼,首先將計數變量count+1,又返回了一個函數,這個函數直接當作callback傳給異步的方法,當執行這個callback的時候,首先是將計數變量count-1,再判斷count是否爲0,若是爲0即表示全部的異步執行完成了,從而達到連續的異步,同一回調的操做。異步

關鍵就在兩個return上,簡單的說:async

第一個return的函數是將count+1,接着返回須要回調的函數函數

第二個return的函數就是須要回調的函數,若是它執行,就是將count-1,而後判斷異步是否所有執行完成,完成了,就回調spa

看個實際點的例子,讀取多個文件的異步回調:線程

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方法包起了咱們想回調執行的方法,當計數器爲0時,就會執行它,那咱們得改進一下pending方法:code

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即爲咱們的回調函數,當var done = pending(callback)時,done其實已爲第一個return的函數,它有一個參數,能夠當作返回的值的下標,因此在循環體中done(fileName[i]),把文件名傳了進去。這個done()是直接執行的,它將count+1後,返回了要傳給異步方法的回調函數,如前面所說,這個回調函數裏會根據計數變量來判斷是否執行咱們但願執行的回調函數,並且把文件的內容傳給了它,即returns。好了,運行一下,相信可以準確的看到運行結果。

0
1
2
3
2
1
0
done
{"1.html": "xxx", "2.html": "xxx", "3.html": "xxx"}

從計數上明顯能看出,從0-3再到0,以後就是咱們的回調函數輸出了done和文件的內容。

這個問題解決了,咱們要思考一下,如何讓這樣的方法封裝重用,否則,每次都寫pending不是很不科學嗎?

下面看看UnJs(個人一個基於NodeJs的Web開發框架)的處理方式,應用於模板解析中的子模板操做:

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有三個參數,意思是:

task: 須要處理的對象,好比須要讀取的文件,它是一個列表,若是不是列表,或列表長度爲0,它將不會執行

func: 異步方法,好比fs.readFile,就是經過它傳進去的

callback: 咱們但願回調的方法

done和前面同理,它傳給了func,但並無執行,由於但願應用端能可控制參數,因此讓應用端去執行。

再看看處理子模板時的操做:

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這個列表,是根據對子模板的解析生成的數據,它是一個二維的數組,每一個子項的第一個值爲子模板的調用文本,即:{% include 'header.html' %}這樣的字符串,第二個參數爲子模板文件名,即:header.html

asyncSeries的第二個參數是的callback,其實是第三個參數,也就是咱們但願執行的回調函數通過pending處理的回調方法,如前面所說,在asyncSeries內部,它並無運行,而是到這裏運行的,即:callback(item[0]),帶上了參數,由於後面還要根據這個參數將父模板中調用子模板的字符串替換爲對應子模板的內容。

這樣子,只要須要連續異步時,就可使用asyncSeries方法來處理了。由於異步的關係,程序的流程有點繞,可能開始不太好理解,即便熟悉了,也有可能忽然想不明白,不要緊,好比,第二個參數中的callback實際是第三個參數生成的,開始可能你就會想,這個callback倒底是啥。還有就是pending的兩個return,也是不太好理解的,須要多想一想。

好了,連續異步的回調使用Js函數的高級特性完成了。但NodeJs的異步性着實讓程序的控制很成問題,諸如還有連續異步,但要傳值的操做等,這些都是能夠經過這樣的思路,變化一下便可實現的。

但願本文對被NodeJs的異步操做搞得頭暈的同窗們有些許的幫助。

相關文章
相關標籤/搜索