首頁 >web前端 >js教程 >深入淺析NodeJs並發非同步的回呼處理_node.js

深入淺析NodeJs並發非同步的回呼處理_node.js

WBOY
WBOY原創
2016-05-16 15:24:121402瀏覽

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

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上,簡單的說:

第一個return的函數是將count+1,接著傳回需要回呼的函數

第二個return的函數就是需要回呼的函數,如果它執行,就是將count-1,然後判斷非同步是否全部執行完成,完成了,就回呼

看個實際點的例子,讀取多個檔案的非同步回呼:

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方法:

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並發異步的回調處理的相關知識,希望大家喜歡。

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn