第三章 簡單回調——3.2 線性化代碼

字號+ 編輯: Snake 修訂: 种花家 來源: 《写给PHP开发者的Node.js学习指南》 2023-09-11 我要說兩句(0)

有時候,非線性代碼可以沖狗狗爲線性代碼。不管你想同步PHP代碼和Node.js,或者拋棄PHP只使用Node.js,都值得我們花費一些時間來重構代碼,這樣代碼中只包含最少的非線性代碼。

看一下之前的非線性代碼的例子:

$fp = fopen('fp.txt','w');
if($msg == 'say'){
  fwrite($fp,'Hello World!'); 
}
$msg = 'done';

if語句模塊中包含PHP API調用,而語句執行完之後又滙總到同一條路經,所以這段代碼是非線性的。如果可以去掉這兩個非線性條件之一,這段代碼就可以具有線性特征了。當移除非線性的時候,需要思考語句的不同路徑的刪除方式。

對if語句來說有兩條路徑:true和false。因爲路徑較少,所以一般採取的方式是簡單地拷貝其餘部分的代碼。如果可以並且這部分代碼不太長,重複的代碼可以隔離不同的路徑,使代碼線性化。之前的非線性PHP代碼會成爲以下線性代碼:

$fp = fopen('fp.txt','w');
if($msg == 'say'){
  fwrite($fp,'Hello World!'); 
  $msg = 'done';
} else {
    $msg = 'done';
  }

添加了else語句,重複了msg變量的賦值語句。代碼現在是線性的了,應用轉換清單進行轉換,如下:

fs.open(‘fp.txt’,’w’,0666,function(error,fp){
  if($msg == 'say'){
    fs.write(fp,’Hello World!’,null,’utf-8’,function(){
      msg = 'done';
    });
  }else{
      msg = 'done';
    }
});

使用重複的手法重構if語句是一個經常使用的創建線性的好辦法。添加一個else語句(如果沒有的話)然後拷貝粘貼其餘的語句分別到if和else語句中就可以了。

其他一些情況下,可以深入理解API本身的調用,重構減少PHP的API調用來移除非線性。看一下之前非線性的for語句示例:

$fp = fopen(‘fp.txt’,’w’);
for($i=0; $i < 3; ++$i){
 fwrite($fp,’Hello World!’);
}

在這個例子中,值得指出的是,找出問題和尋求可能的解決方案是非令人苦惱的過程。當循環展開的時候,很明顯fs.write()調用不能繼續放在循環裡,需要鏈式嵌套在回調函數中:

fs.open(‘fp.txt’,’w’,0666,function(error,fp){
  fs.write(fp,’Hello World!’,null,’utf-8’,function(){
    fs.write(fp,’Hello World!’,null,’utf-8’,function(){
      fs.write(fp,’Hello World!’,null,’utf-8’,function(){
      });
    });
  });
});

如果循環次數是一個離散的數字,循環可以被展開。但是如果循環次數是一個變量,就不可能再以這種方式展開循環了。

幸運的是,通過研究fwrite() PHP API,我們了解到多次調用fwrite() API可以通過一次調用寫入所有數據替換。所以PHP代碼重構後,如下所示:

$fp = fopen('fp.txt','w');
$data = '';
for($i=0;$i<3;++$i){
  $data .= 'Helo World!';
}
fwrite($fp,$data);

需要寫入文档的數據都放在data變量中,用一次fwrite() API調用寫入文档。重構的代碼具有線性特征,線性的PHP代碼可以使用轉換清單:

fs.open(‘fp.txt’,’w’,0666,function(error,fp){
  var data  = '';
  for (var i=0;i<3;++i){
    data += 'Hello World!';
  }
  fs.write(fp,data,null,'utf-8',function(){
  });
});

一行行分析PHP代碼,找出非線性的代碼並重構爲非線性的過程,是將PHP代碼轉換到Node.js準備過程的第一步,是必要步驟,值得我們花點時間。到現在,你已經學習了如何將簡單語句、條件語句和阻塞型PHP調用轉換爲更容易移植的PHP代碼,最終稱爲非阻塞型的Node.js代碼。但是PHP代碼中處理語句和API調用之外還有其他的内容:比如函數和函數調用。

如果你還記得,本章開始的時候我們曾說過,Node.js是如何在整個行業多年形成的共識上建立自己内建函數庫和API的模型,可以一直追溯到原始C API 的命名、函數和操作。當嘗試如何將阻塞型的PHP API調用轉換爲非阻塞型的Node.js API時,基本依賴於每一個PHP API找到類似的Node.js API。這種方式絕大部分時候都行得通,即使行不通,也很容易在現有的Node.js API的基礎上構建需要的Node.js API,一樣可以工作。

但是,現有代碼中的PHP函數,相對於内建的阻塞型PHP函數來說,要難處理得多。很難預測這些函數是否使用阻塞性的PHP API,所以很難決定是否需要回調函數。

來看一個用戶自定義的PHP函數,prefixAndConcat()。該函數接收兩個參數:字符串和數組,返回一個字符串。返回值是一個長字符串,將字符串參數拼接在每個數組元素之前,然後將所有拼接字符串連在一起。例如,字符串參數爲prefix_,數組參數爲['a','b','c'],返回的結果爲prefix_aprefix_bprefix_c:

function prefixAndConcat($str,$a){
  //unknow code that may or may not call blocking PHP APIS
}
$n = prefixAndConcat('prefix_',array('a','b','c'));
$msg = 'prefixd string: '.$n; //last statement before PHP exit point

將prefixAndConcat()函數與另一個用戶自定義的PHP函數進行對比,調用該函數可以輸出多次“Hello World!”到某一個文档,並將返回值保存在msg變量中:

function writeMultipleStrings($str,$a){
  //unknow code that may or may not call blocking PHP APIS
}
$n = writeMultipleStrings('fp.txt',array('a','b','c'));
$msg = 'wrote '.$n.'times'; //last statement before PHP exit point

這兩個函數看起來非常相似。但是,prefixAndConcat()函數隻操作字符串,即使在Node.js中也不會需要非阻塞型的API調用。但是,writeMultipleStrings()函數則需要使用非阻塞型的Node.js文档處理API。

當轉換爲Node.js時,prefixAndConcat()函數的調用如下:

var n = prefixAndConcat('prefix_',array('a','b','c'));
var msg = 'prefixd string: '+n; //last statement before PHP exit point

作爲對比,writeMultipleStrings()函數的調用如下:

writeMultipleStrings('fp.txt',array('a','b','c'),function(){
  var msg = 'wrote'+n+'times';//last statement before PHP exit point
});

對我們而言的挑戰是,需要將PHP表達式的返回值作爲Node.js回調函數的參數來追蹤什麽時候PHP和Node.js代碼維持一致並且同步。這是另一個需要跟蹤的非線性的Node.js函數,並理解它如何影響代碼的線性特征。

一種處理提供符合槼範的命名,可以通過命名傳遞用戶定義的函數在Node.js中是阻塞或者非阻塞的(PHP中總是阻塞型的)。在函數名中添加些信息,可以更容易地在瀏覽轉換錢的PHP代碼時預計到轉換後的Node.js看起來是什麽樣子。一個轉換方法是在每個調用非阻塞型Node.js API的PHP自定義函數名末尾加上_cb(cb表示callback)。

所以,writeMultipleStrings()重命名爲writeMultipleStrings_cb():

function writeMultipleStrings_cb($str,$a){
  //unknow code that may or may not call blocking PHP APIS
}

如果按照這種方式轉換,很明顯,writeMultipleStrings_cb()的Node.js調用代碼爲:

writeMultipleStrings_cb('fp.txt',array('a','b','c'),function(n){
  var msg = 'wrote'+n+'times';//last statement before PHP exit point
});

prefixAndConcat()函數不發生任何變化,名字裡也沒有_cb:

var n = prefixAndConcat('prefix_',['a','b','c']);
var msg = 'prefixd string: '+n; //last statement before PHP exit point

這個轉換方法只對相對簡單的PHP代碼有效,但是在較複雜的PHP代碼中,幾乎每個用戶自定義的函數都會使用阻塞型的PHP API。對每個這樣的函數添加_cb並沒有太大用處。只有一些PHP函數名包含_cb而另一些沒有,才會使這個標識符有意義。

另一種更有效的轉換策略是限制包括阻塞型PHP API調用函數的嵌套。如果一個PHP用戶自定義的函數A調用了另一個函數B,函數B又調用了函數C,以此類推,我們稱這種爲深層嵌套。如果調用只有一層或者兩層,則稱爲淺嵌套。在一般的PHP代碼中,如果目的不是移植到Node.js,是需要深層次嵌套的,因爲低層次的函數需要高層次函數産生的細節。但是,分析和移除PHP的非線性,維持與Node.js版本的同步,則需要淺嵌套關係。當處理非線性問題時需要在各個層面理解代碼,這是必需的。抽象在這種時候是個障礙,不是幫助。

刪除抽象,使PHP代碼更容易分析和重構爲線性代碼的一種方式是展開嵌套的PHP用戶自定義函數。這種轉換方式移除嵌套關係,更容易對PHP代碼進行分析和重構。思考一下writeMultipleStrings()函數的實現。之前的部分介紹過函數的用途,但是以下是該函數的實際實現:

function writeMultipleStrings_cb($str,$a){
  $fp = fopen($str,'w');
  $data = '';
  for($i=0;$i<count($a);++$i){
    $data .=$a[$a];
  }
  fwrite($fp,$data);
  fclose($fp);
  return $data;
}
$n = writeMultipleStrings('fp.txt',array('a','b','c'));
$msg = 'wrote'.$n.'data';//last statement before PHP exit point

爲了展開該函數,拷貝粘貼writeMultipleStrings()的實現到調用的地方。如果可以保留變量名,則可以在實現的開始聲明變量並且賦值。在實現的末尾將返回值賦給該變量:

$str = 'fp.txt';//argument #1
$a = array('a','b','c');//argument #2
$fp = fopen($str,'w');
$data = '';
for($i=0;$i<count($a);++$i){
  $data .=$a[$i];
}
fwrite($fp,$data);
fclose($fp);
$n = $data;//return value
$m  = 'wrote'.$n.'data';//last statement before PHP exit point

展開writeMultipleStrings()調用之後,很容易使這段代碼線性化。對應的Node.js代碼爲:

var str = 'fp.txt';//argument #1
var a = ['a','b','c'];//argument #2
fs.open($str,'w',0666,function(error,fp){
  var data = '';
  for (var i=0;i<a.length;++i){
    data += a[i];
  }
  fs.write(fp,data,null,'utf-8',function(){
    fs.close(fp,function(){
     var n = data;//return value
     var msg  = 'wrote'.$n.'data';//last statement before PHP exit point
    });
  });
});

你可以看到,展開PHP函數調用可以減少嵌套的層次,是代碼更容易移植。

還有第二種減少嵌套層次方法不太好實現,通過檢視PHP代碼,看看有沒有可能減少嵌套關係,集中注意力在那個可能調用阻塞型PHP API的用戶自定義函數上。那些沒有調用阻塞型PHP API的自定義函數則沒有關係。通過仔細的檢查和重構來避免不必要的自定義函數,PHP帶代碼移植到Node.js的過程或更容易些。

如本章介紹的一樣,PHP到Node.js的代碼移植過程的一個重要的方面就是重構PHP代碼,使其更容易進行移植。在PHP代碼中可以很好地處理線性,所以Node.js和PHP的關係是平行並且緊密的。

我們的預期是PHP和Node.js代碼可以保持同步並且並行改進,開發人員可以根據自己對線性特性和其他一些Node.js轉換問題的理解來編寫新的特性,同時關注保持兩個代碼庫之間的轉換。

代碼的線性化是我們的目標,但並不是一定都能實現的。本章中介紹了一些基本的轉換方法,將非線性的PHP代碼轉換爲線性。但是還是有些非線性的代碼需要更複雜的轉換策略來處理。

一些for語句非常有挑戰性。當一個需要移植到Node.js的for語句包含阻塞型PHP API調用,需要轉換爲一個非阻塞型的Node.js API調用,就會産生線性的問題:

$fp = fopen($str,'w');
for($i=0;$i<count($a);++$i){
  fwrite($fp,'Hello World!');
}

當這段代碼轉換爲Node.js時,for語句完全消失,而由一個回調函數鏈的結構代替:

fs.open(‘fp.txt’,’w’,0666,function(error,fp){
  fs.write(fp,’Hello World!’,null,’utf-8’,function(){
    fs.write(fp,’Hello World!’,null,’utf-8’,function(){
      fs.write(fp,’Hello World!’,null,’utf-8’,function(){
        //and on and on for a total of "n" times
      });
    });
  });
});

本章開始的部分介紹了一種轉換方法來刪除循環中的阻塞型PHP API調用。深入研究一下Node.js fs.write() API時如何工作的,阻塞性的PHP API 調用可以移到for語句之外。所以不需要將for語句轉換爲一個回調函數鏈,可以保留for語句,但是只在一條路徑上進行疊代,循環結束後,調用一次Node.js fs.write()。關鍵之處在於重構代碼,將多次Node.js fs.write()調用改爲一次。沒有了Node.js回調函數鏈,代碼功能相同,看起來更像原始的PHP代碼:

fs.open($str,'w',0666,function(error,fp){
  var data = '';
  for (var i=0;i<n;++i){
    data += 'Hello World!';
  }
  fs.write(fp,data,null,'utf-8',function(){
  });
});

如果深入研究沒有結果呢?可能沒有辦法從for語句中刪除阻塞型的PHP API。不同於之前的對同一個文档多次寫入不同數據,考慮另一種情況,利用for語句將同一段數據寫入任意數目的不同文档(比如,一堆日志文档):

for($i=0; $i < 3; ++$i){
  $fp = fopen($str+$i,'w');
  fwrite($fp,$data);
  fclose($fp);
}

不經過重構直接轉換Node.js,for語句由回調函數鏈替換:

fs.open(‘fp.txt’,’w’,0666,function(error,fp){
  fs.write(fp,data,null,’utf-8’,function(){
    fs.close(fp,function(error){
      fs.open(fp,data,null,’utf-8’,function(){
        fs.close(fp,function(){
          //and on and on for a total of "n" times
        });
      });
    });
  });
});

怎樣避免回調函數鏈呢?需要單獨對每個文档進行寫操作。沒有特定的非阻塞型Node.js API可以在一個回調函數中對多個文档驚醒寫操作。如果需要對每個文档愛你單獨進行寫操作,就一定會多次調用Node.js API,每次調用都有自己的回調函數,而且嵌套在前一次調用的回調函數中。

在下一章中,會介紹一種更複雜的轉換方式來解決這種問題。這種轉換方式包含PHP continuation(時序),一種在PHP代碼(甚至PHP4代碼)中模擬回調的方式,但是不支持PHP5中添加的有助於支持Node.js風格回調的新語言特性(比如,lambda和匿名函數)。

閲完此文,您的感想如何?
  • 有用

    0

  • 沒用

    0

  • 開心

    0

  • 憤怒

    0

  • 可憐

    0

1.如文章侵犯了您的版權,請發郵件通知本站,該文章將在24小時内刪除;
2.本站標注原創的文章,轉發時煩請注明來源;
3.交流群: 2702237 13835667

相關課文
  • JS如何防止父節點的事件運行

  • nodejs編寫一個簡單的http請求客戶耑代碼demo

  • 說一則爲什麽後耑開發人員不選擇node.js的原因

  • 使用Sublime Text3 開發React-Native的配置

我要說說
網上賓友點評