本文是我在研究 PHP 異步編程時的總結。對於相當多的 PHPer 來説,可能都不知道 Generator,或者對 Generaotr 的流程不是很熟悉。因為 Generator 使得程序不再是順序的。鑑於本人的水平有限,如果有不同意見,還望指點一二,不勝感激!
PHP 中的異常處理
從 PHP 5 開始,PHP 為我們提供了 try catch 來進行異常處理。當我們使用 catch 將異常捕獲,那麼一場後續的代碼就會執行。我們看看下面的例子。
try {
throw new Exception('e');
} catch (Exception $e) {
echo $e->getMessage(); // output: e
}
echo 2; // output: 2
如果我們沒有將異常捕獲,那麼後面的代碼就不會執行了。
throw new Exception('e'); // throw an exception
echo 2; // not execute
Generator 的 throw 方法
在 PHP 中,Generator 提供了 throw 方法來拋出異常。用法和普通的異常一樣,只不過把 throw 關鍵字改成了方法調用。
function gen()
{
yield 0;
yield 1;
yield 2;
yield 3;
}
$gen = gen();
$gen->throw(new Exception('e')); // throw an exception
var_dump($gen->valid()); // output: false
echo 2; // not execute
同樣的,我們可以這個異常捕獲,通過 try catch 來進行。
try {
$gen->throw(new Exception('e'));
} catch (Exception $e) {
echo $e->getMessage(); // output: e
}
var_dump($gen->valid()); // output: false
echo 2; // output: 2
我們可以看到,當我們使用 throw 拋出異常後,當前的生成器的 valid 變成了 false。但是考慮下面一種情況,當我們在外面調用 throw 方法後,在生成器函數中捕獲異常,會發生什麼呢?我們來看下面的例子。
function gen()
{
yield 0;
try {
yield;
} catch (Exception $e) {
echo $e->getMessage(); // output: e
}
yield 2;
yield 3;
}
$gen = gen();
$gen->next(); // reach the point of catching exception
$gen->throw(new Exception('e'));
var_dump($gen->valid()); // output: true
echo 2; // output: 2
當我們在生成器函數捕獲來自 throw 方法拋出的異常後,生成器依然是 valid 的。但是如果像剛才一樣只是在調用 throw 方法,那麼生成器就結束了。
在生成器函數中拋出異常
function gen()
{
yield 0;
throw new Exception('e');
yield 2;
yield 3;
}
$gen = gen();
$gen->next();
$gen->current(); // throw an exception
var_dump($gen->valid()); // output: false
echo 2; // not execute
之前我們看到的是調用 throw 方法來拋出異常。那麼在生成器函數中,拋出一個異常而沒有在生成器函數中捕獲,結果也都是一樣的。同樣的,如果在生成器函數中捕獲了異常,那麼就和之前的例子一樣了。
在理解了上面的例子之後,我們就要考慮一下,如果有嵌套的生成器,會發生什麼了。
嵌套生成器
當我們在一個生成器函數中,yield 了另外一個生成器函數之後,就會變成嵌套生成器。我們來看下面的例子。
function subGen()
{
yield 1;
throw new Exception('e');
yield 4;
}
function gen()
{
yield 0;
yield subGen();
yield 2;
yield 3;
}
$gen = gen();
$gen->next();
$gen->current()->next(); // throw an exception
echo 2; // not execute
對於嵌套的生成器來説,如果子生成器中拋出了異常,那麼在沒有捕獲這個異常的情況下,會一級一級向上拋出,直到結束。
剛才我們嘗試了,在拋出異常之後,valid 的返回值變成了 false。那麼在嵌套生成器中,是不是也是這樣呢?我們把異常捕獲,使程序能夠繼續執行下去,來看下面這個例子。
function subGen()
{
yield 1;
throw new Exception('e');
yield 4;
}
function gen()
{
yield 0;
yield subGen();
yield 2;
yield 3;
}
$gen = gen();
$gen->next();
try {
$gen->current()->next();
} catch (Exceprion $e) {
echo $e->getMessage(); //output: e
}
var_dump($gen->valid()); // output: true
echo 2; // output: 2
所以,當子生成器拋出異常後在迭代的過程中被正常地捕獲,那麼,父生成器便不會受到影響,valid 的返回值依然是 true。
總結
關於生成器的異常處理,這裏來進行一下總結。
- 在生成器中拋出一個異常,或者使用 throw 方法拋出一個異常,那麼,生成器的迭代便會結束,valid 變成
false; - 在生成器中拋出一個異常,迭代過程中對異常進行捕獲,生成器的迭代依然會結束,valid 依然會變成
false; - 在生成器中拋出一個異常,在生成器中將其捕獲處理,生成器的迭代不會結束,valid 會返回
true; - 在嵌套的生成器中,如果子生成器拋出了異常,只會對子生成器產生影響,不會對父生成器產生影響。
後記
yield 為我們提供了使用 PHP 實現半協程的工具。最近在研究使用 yield 實現半協程,而這個過程中,對異常的處理,是非常重要的。但是 yield 的運行方式決定了異常處理比較難以理解。於是我花了幾天的時間,嘗試了各種可能,得出來的這些結論。當然由於本人水平有限,如有錯誤,還望指點一二,不勝感激。