PHP下的非同步嘗試四:PHP版的Promise
阿新 • • 發佈:2018-11-16
PHP下的非同步嘗試系列
如果你還不太瞭解PHP下的生成器和協程,你可以根據下面目錄翻閱
- PHP下的非同步嘗試一:初識生成器
- PHP下的非同步嘗試二:初識協程
- PHP下的非同步嘗試三:協程的PHP版thunkify自動執行器
- PHP下的非同步嘗試四:PHP版的Promise
- PHP下的非同步嘗試五:PHP版的Promise的繼續完善
Promise 實現
程式碼結構
│ │ autoload.php │ │ promise1.php │ │ promise2.php │ │ promise3.php │ │ promise4.php │ │ promise5.php │ │ │ └─classes │ Promise1.php │ Promise2.php │ Promise3.php │ Promise4.php │ Promise5.php │ PromiseState.php
嘗試一 (Promise基礎)
classess/PromiseState.php
final class PromiseState
{
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
}
classess/Promise1.php
// 嘗試一 class Promise1 { private $value; private $reason; private $state; public function __construct(\Closure $func = null) { $this->state = PromiseState::PENDING; $func([$this, 'resolve'], [$this, 'reject']); } /** * 執行回撥方法裡的resolve繫結的方法 * @param null $value */ public function resolve($value = null) { // 回撥執行resolve傳參的值,賦值給result $this->value = $value; if ($this->state == PromiseState::PENDING) { $this->state = PromiseState::FULFILLED; } } public function reject($reason = null) { // 回撥執行resolve傳參的值,賦值給result $this->reason = $reason; if ($this->state == PromiseState::PENDING) { $this->state = PromiseState::REJECTED; } } public function getState() { return $this->state; } public function getValue() { return $this->value; } public function getReason() { return $this->reason; } }
promise1.php
require "autoload.php";
$promise = new Promise1(function($resolve, $reject) {
$resolve("列印我");
});
var_dump($promise->getState());
var_dump($promise->getValue());
結果:
string(9) "fulfilled"
string(9) "列印我"
結論或問題:
我們在這裡建構了最基礎的Promise模型
嘗試二 (增加鏈式then)
classess/Promise2.php
<?php
// 嘗試二 (增加鏈式then)
class Promise2
{
private $value;
private $reason;
private $state;
public function __construct(\Closure $func = null)
{
$this->state = PromiseState::PENDING;
$func([$this, 'resolve'], [$this, 'reject']);
}
public function then(\Closure $onFulfilled = null, \Closure $onRejected = null)
{
// 如果狀態是fulfilled,直接回調執行並傳參value
if ($this->state == PromiseState::FULFILLED) {
$onFulfilled($this->value);
}
// 如果狀態是rejected,直接回調執行並傳參reason
if ($this->state == PromiseState::REJECTED) {
$onRejected($this->reason);
}
// 返回物件自身,實現鏈式呼叫
return $this;
}
/**
* 執行回撥方法裡的resolve繫結的方法
* 本狀態只能從pending->fulfilled
* @param null $value
*/
public function resolve($value = null)
{
if ($this->state == PromiseState::PENDING) {
$this->state = PromiseState::FULFILLED;
$this->value = $value;
}
}
/**
* 執行回撥方法裡的rejected繫結的方法
* 本狀態只能從pending->rejected
* @param null $reason
*/
public function reject($reason = null)
{
if ($this->state == PromiseState::PENDING) {
$this->state = PromiseState::REJECTED;
$this->reason = $reason;
}
}
public function getState()
{
return $this->state;
}
public function getValue()
{
return $this->value;
}
public function getReason()
{
return $this->reason;
}
}
promise2.php
<?php
require "autoload.php";
$promise = new Promise2(function($resolve, $reject) {
$resolve("列印我");
});
$promise->then(function ($value) {
var_dump($value);
}, function ($reason) {
var_dump($reason);
})->then(function ($value) {
var_dump($value);
}, function ($reason) {
var_dump($reason);
});
結果:
string(9) "列印我"
string(9) "列印我"
結論或問題:
我們實現了鏈式then方法
如果我們的構造裡的回撥是非同步執行的話,那麼狀態在沒有變成fulfilled之前,我們then裡的回撥方法就永遠沒法執行
嘗試三(真正的鏈式then)
classess/Promise3.php
// 解決思路:我們肯定要把then傳入的回撥,放到Promise構造裡回撥程式碼執行完後resolve呼叫後改變了state狀態後再呼叫,所以我們必須儲存到一個地方並方便後續呼叫
// 我們需要改造then、resolve和reject方法
class Promise3
{
private $value;
private $reason;
private $state;
private $fulfilledCallbacks = [];
private $rejectedCallbacks = [];
public function __construct(\Closure $func = null)
{
$this->state = PromiseState::PENDING;
$func([$this, 'resolve'], [$this, 'reject']);
}
public function then(\Closure $onFulfilled = null, \Closure $onRejected = null)
{
// 如果是非同步回撥,狀態未變化之前,then的回撥方法壓入相應的陣列方便後續呼叫
if ($this->state == PromiseState::PENDING) {
$this->fulfilledCallbacks[] = static function() use ($onFulfilled, $value){
$onFulfilled($this->value);
};
$this->rejectedCallbacks[] = static function() use ($onRejected, $reason){
$onRejected($this->reason);
};
}
// 如果狀態是fulfilled,直接回調執行並傳參value
if ($this->state == PromiseState::FULFILLED) {
$onFulfilled($this->value);
}
// 如果狀態是rejected,直接回調執行並傳參reason
if ($this->state == PromiseState::REJECTED) {
$onRejected($this->reason);
}
// 返回物件自身,實現鏈式呼叫
return $this;
}
/**
* 執行回撥方法裡的resolve繫結的方法
* 本狀態只能從pending->fulfilled
* @param null $value
*/
public function resolve($value = null)
{
if ($this->state == PromiseState::PENDING) {
$this->state = PromiseState::FULFILLED;
$this->value = $value;
array_walk($this->fulfilledCallbacks, function ($callback) {
$callback();
});
}
}
/**
* 執行回撥方法裡的rejected繫結的方法
* 本狀態只能從pending->rejected
* @param null $reason
*/
public function reject($reason = null)
{
if ($this->state == PromiseState::PENDING) {
$this->state = PromiseState::REJECTED;
$this->reason = $reason;
}
}
public function getState()
{
return $this->state;
}
public function getValue()
{
return $this->value;
}
public function getReason()
{
return $this->reason;
}
}
promise3.php
require "autoload.php";
$promise = new Promise3(function($resolve, $reject) {
$resolve("列印我");
});
$promise->then(function ($value) {
var_dump($value);
}, function ($reason) {
var_dump($reason);
})->then(function ($value) {
var_dump($value);
}, function ($reason) {
var_dump($reason);
});
結果:
string(9) "列印我"
string(9) "列印我"
結論或問題:
我們這次基本實現了真正的鏈式then方法
不過在Promise/A+裡規範,要求then返回每次都要求是一個新的Promise物件
then方法成功執行,相當於返回一個例項一個Promise回撥裡執行resolve方法,resolve值為then裡return的值
then方法執行失敗或出錯,相當於返回一個例項一個Promise回撥裡執行rejected方法,rejected值為then裡return的值
嘗試四(then返回pormise物件, 並傳遞上一次的結果給下一個Promise物件)
classess/Promise4.php
class Promise4
{
private $value;
private $reason;
private $state;
private $fulfilledCallbacks = [];
private $rejectedCallbacks = [];
public function __construct(\Closure $func = null)
{
$this->state = PromiseState::PENDING;
$func([$this, 'resolve'], [$this, 'reject']);
}
public function then(\Closure $onFulfilled = null, \Closure $onRejected = null)
{
$thenPromise = new Promise4(function ($reslove, $reject) use (&$thenPromise, $onFulfilled, $onRejected) {
//$this 代表的當前的Promise物件,不要混淆了
// 如果是非同步回撥,狀態未變化之前,then的回撥方法壓入相應的陣列方便後續呼叫
if ($this->state == PromiseState::PENDING) {
$this->fulfilledCallbacks[] = static function() use ($thenPromise, $onFulfilled, $reslove, $reject){
$value = $onFulfilled($this->value);
$this->resolvePromise($thenPromise, $value, $reslove, $reject);
};
$this->rejectedCallbacks[] = static function() use ($thenPromise, $onRejected, $reslove, $reject){
$reason = $onRejected($this->reason);
$this->resolvePromise($thenPromise, $reason, $reslove, $reject);
};
}
// 如果狀態是fulfilled,直接回調執行並傳參value
if ($this->state == PromiseState::FULFILLED) {
$value = $onFulfilled($this->value);
$this->resolvePromise($thenPromise, $value, $reslove, $reject);
}
// 如果狀態是rejected,直接回調執行並傳參reason
if ($this->state == PromiseState::REJECTED) {
$reason = $onRejected($this->reason);
$this->resolvePromise($thenPromise, $reason, $reslove, $reject);
}
});
// 返回物件自身,實現鏈式呼叫
return $thenPromise;
}
/**
* 解決Pormise鏈式then傳遞
* 可參考 [Promises/A+]2.3 [https://promisesaplus.com/#the-promise-resolution-procedure]
* @param $thenPromise
* @param $x $x為thenable物件
* @param $resolve
* @param $reject
*/
private function resolvePromise($thenPromise, $x, $resolve, $reject)
{
$called = false;
if ($thenPromise === $x) {
return $reject(new \Exception('迴圈引用'));
}
if ( is_object($x) && method_exists($x, 'then')) {
$resolveCb = function ($value) use($thenPromise, $resolve, $reject, $called) {
if ($called) return ;
$called = true;
// 成功值y有可能還是promise或者是具有then方法等,再次resolvePromise,直到成功值為基本型別或者非thenable
$this->resolvePromise($thenPromise, $value, $resolve, $reject);
};
$rejectCb = function($reason) use($thenPromise, $resolve, $reject, $called) {
if ($called) return ;
$called = true;
$reject($reason);
};
call_user_func_array([$x, 'then'], [$resolveCb, $rejectCb]);
} else {
if ($called) return ;
$called = true;
$resolve($x);
}
}
/**
* 執行回撥方法裡的resolve繫結的方法
* 本狀態只能從pending->fulfilled
* @param null $value
*/
public function resolve($value = null)
{
if ($this->state == PromiseState::PENDING) {
$this->state = PromiseState::FULFILLED;
$this->value = $value;
array_walk($this->fulfilledCallbacks, function ($callback) {
$callback();
});
}
}
/**
* 執行回撥方法裡的rejected繫結的方法
* 本狀態只能從pending->rejected
* @param null $reason
*/
public function reject($reason = null)
{
if ($this->state == PromiseState::PENDING) {
$this->state = PromiseState::REJECTED;
$this->reason = $reason;
}
}
public function getState()
{
return $this->state;
}
public function getValue()
{
return $this->value;
}
public function getReason()
{
return $this->reason;
}
}
promise4.php
require "autoload.php";
$promise1 = new Promise4(function($resolve, $reject) {
$resolve("列印我");
});
$promise2 = $promise1->then(function ($value) {
var_dump($value);
return "promise2";
}, function ($reason) {
var_dump($reason);
});
$promise3 = $promise2->then(function ($value) {
var_dump($value);
return new Promise4(function($resolve, $reject) {
$resolve("promise3");
});
}, function ($reason) {
var_dump($reason);
});
$promise4 = $promise3->then(function ($value) {
var_dump($value);
return "promise4";
}, function ($reason) {
var_dump($reason);
});
var_dump($promise4);
結果:
string(9) "列印我"
string(8) "promise2"
string(8) "promise3"
object(Promise4)#15 (5) {
["value":"Promise4":private]=>
string(8) "promise4"
["reason":"Promise4":private]=>
NULL
["state":"Promise4":private]=>
string(9) "fulfilled"
["fulfilledCallbacks":"Promise4":private]=>
array(0) {
}
["rejectedCallbacks":"Promise4":private]=>
array(0) {
}
}
結論或問題:
一個基本的Pormise,不過我們上面都是基於成功fulfilled狀態的實現
下面我們來增加錯誤捕獲
嘗試五(錯誤捕獲)
classess/Promise5.php
class Promise5
{
private $value;
private $reason;
private $state;
private $fulfilledCallbacks = [];
private $rejectedCallbacks = [];
public function __construct(\Closure $func = null)
{
$this->state = PromiseState::PENDING;
$func([$this, 'resolve'], [$this, 'reject']);
}
public function then(\Closure $onFulfilled = null, \Closure $onRejected = null)
{
// 此處作用是相容then方法的以下四種引數變化,catchError就是第二種情況
// 1. then($onFulfilled, null)
// 2. then(null, $onRejected)
// 3. then(null, null)
// 4. then($onFulfilled, $onRejected)
$onFulfilled = is_callable($onFulfilled) ? $onFulfilled : function ($value) {return $value;};
$onRejected = is_callable($onRejected) ? $onRejected : function ($reason) {throw $reason;};
$thenPromise = new Promise5(function ($reslove, $reject) use (&$thenPromise, $onFulfilled, $onRejected) {
//$this 代表的當前的Promise物件,不要混淆了
// 如果是非同步回撥,狀態未變化之前,then的回撥方法壓入相應的陣列方便後續呼叫
if ($this->state == PromiseState::PENDING) {
$this->fulfilledCallbacks[] = static function() use ($thenPromise, $onFulfilled, $reslove, $reject){
try {
$value = $onFulfilled($this->value);
$this->resolvePromise($thenPromise, $value, $reslove, $reject);
} catch (\Exception $e) {
$reject($e);
}
};
$this->rejectedCallbacks[] = static function() use ($thenPromise, $onRejected, $reslove, $reject){
try {
$reason = $onRejected($this->reason);
$this->resolvePromise($thenPromise, $reason, $reslove, $reject);
} catch (\Exception $e) {
$reject($e);
}
};
}
// 如果狀態是fulfilled,直接回調執行並傳參value
if ($this->state == PromiseState::FULFILLED) {
try {
$value = $onFulfilled($this->value);
$this->resolvePromise($thenPromise, $value, $reslove, $reject);
} catch (\Exception $e) {
$reject($e);
}
}
// 如果狀態是rejected,直接回調執行並傳參reason
if ($this->state == PromiseState::REJECTED) {
try {
$reason = $onRejected($this->reason);
$this->resolvePromise($thenPromise, $reason, $reslove, $reject);
} catch (\Exception $e) {
$reject($e);
}
}
});
// 返回物件自身,實現鏈式呼叫
return $thenPromise;
}
public function catchError($onRejected)
{
return $this->then(null, $onRejected);
}
/**
* 解決Pormise鏈式then傳遞
* 可參考 [Promises/A+]2.3 [https://promisesaplus.com/#the-promise-resolution-procedure]
* @param $thenPromise
* @param $x $x為thenable物件
* @param $resolve
* @param $reject
*/
private function resolvePromise($thenPromise, $x, $resolve, $reject)
{
$called = false;
if ($thenPromise === $x) {
return $reject(new \Exception('迴圈引用'));
}
if ( is_object($x) && method_exists($x, 'then')) {
try {
$resolveCb = function ($value) use ($thenPromise, $resolve, $reject, $called) {
if ($called) return;
$called = true;
// 成功值y有可能還是promise或者是具有then方法等,再次resolvePromise,直到成功值為基本型別或者非thenable
$this->resolvePromise($thenPromise, $value, $resolve, $reject);
};
$rejectCb = function ($reason) use ($thenPromise, $resolve, $reject, $called) {
if ($called) return;
$called = true;
$reject($reason);
};
call_user_func_array([$x, 'then'], [$resolveCb, $rejectCb]);
} catch (\Exception $e) {
if ($called) return ;
$called = true;
$reject($e);
}
} else {
if ($called) return ;
$called = true;
$resolve($x);
}
}
/**
* 執行回撥方法裡的resolve繫結的方法
* 本狀態只能從pending->fulfilled
* @param null $value
*/
public function resolve($value = null)
{
if ($this->state == PromiseState::PENDING) {
$this->state = PromiseState::FULFILLED;
$this->value = $value;
array_walk($this->fulfilledCallbacks, function ($callback) {
$callback(); //因為回撥本身攜帶了作用於,所以直接呼叫,無法引數
});
}
}
/**
* 執行回撥方法裡的rejected繫結的方法
* 本狀態只能從pending->rejected
* @param null $reason
*/
public function reject($reason = null)
{
if ($this->state == PromiseState::PENDING) {
$this->state = PromiseState::REJECTED;
$this->reason = $reason;
array_walk($this->rejectedCallbacks, function ($callback) {
$callback(); //因為回撥本身攜帶了作用於,所以直接呼叫,無法引數
});
}
}
public function getState()
{
return $this->state;
}
public function getValue()
{
return $this->value;
}
public function getReason()
{
return $this->reason;
}
}
promise5.php
require "autoload.php";
$promise1 = new Promise5(function($resolve, $reject) {
$resolve("列印我");
});
$promise2 = $promise1->then(function ($value) {
var_dump($value);
throw new \Exception("promise2 error");
return "promise2";
}, function ($reason) {
var_dump($reason->getMessage());
return "promise3 error return";
});
//我們可以簡寫then方法,只傳入$onFulfilled方法,然後錯誤會自己冒泡方式到下一個catchError或then裡處理。
//$promise3 = $promise2->then(function ($value) {
// var_dump($value);
// return new Promise5(function($resolve, $reject) {
// $resolve("promise3");
// });
//})->catchError(function ($reason) {
// var_dump($reason->getMessage());
// return "promise3 error return";
//});
$promise3 = $promise2->then(function ($value) {
var_dump($value);
return new Promise5(function($resolve, $reject) {
$resolve("promise3");
});
}, function ($reason) {
var_dump($reason->getMessage());
return "promise3 error return";
});
$promise4 = $promise3->then(function ($value) {
var_dump($value);
return "promise4";
}, function ($reason) {
echo $reason->getMessage();
});
var_dump($promise4);
結果:
string(9) "列印我"
string(14) "promise2 error"
string(21) "promise3 error return"
object(Promise4)#10 (5) {
["value":"Promise4":private]=>
string(8) "promise4"
["reason":"Promise4":private]=>
NULL
["state":"Promise4":private]=>
string(9) "fulfilled"
["fulfilledCallbacks":"Promise4":private]=>
array(0) {
}
["rejectedCallbacks":"Promise4":private]=>
array(0) {
}
}
結論或問題:
這裡我們基礎實現了一個可以用於生產環境的Promise
後續我們會接續完善這個Promise的特有方法,比如:finally, all, race, resolve, reject等
後續再介紹用Promise實現的自動執行器等
附錄參考
Promises/A+
Promises/A+ 中文
Promise 物件 - ECMAScript 6 入門 阮一峰
原文地址:https://segmentfault.com/a/1190000016564649