1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Gandung\Promise; |
4
|
|
|
|
5
|
|
|
use TaskQueue\TaskQueue; |
6
|
|
|
|
7
|
|
|
class Promise implements PromiseInterface |
8
|
|
|
{ |
9
|
|
|
/** |
10
|
|
|
* @var integer |
11
|
|
|
*/ |
12
|
|
|
private $state = self::STATE_PENDING; |
13
|
|
|
|
14
|
|
|
/** |
15
|
|
|
* @var mixed |
16
|
|
|
*/ |
17
|
|
|
private $current; |
18
|
|
|
|
19
|
|
|
/** |
20
|
|
|
* @var array |
21
|
|
|
*/ |
22
|
|
|
private $context = []; |
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* @var \Closure |
26
|
|
|
*/ |
27
|
|
|
private $waitCallback; |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* @var \Closure |
31
|
|
|
*/ |
32
|
|
|
private $cancelCallback; |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* @var boolean |
36
|
|
|
*/ |
37
|
|
|
private $isStateTransformed; |
38
|
|
|
|
39
|
|
|
public function then($onFulfilled = null, $onRejected = null) |
40
|
|
|
{ |
41
|
|
|
if ($this->state === self::STATE_PENDING) { |
42
|
|
|
$q = new Promise(); |
43
|
|
|
$this->context[] = [$q, $onFulfilled, $onRejected]; |
44
|
|
|
|
45
|
|
|
return $q; |
46
|
|
|
} |
47
|
|
|
|
48
|
|
|
if ($this->state === self::STATE_FULFILLED) { |
49
|
|
|
return $onFulfilled |
50
|
|
|
? (new FulfilledPromise($this->current))->then($onFulfilled, null) |
51
|
|
|
: new FulfilledPromise($this->current); |
52
|
|
|
} |
53
|
|
|
|
54
|
|
|
return $onRejected |
55
|
|
|
? (new RejectedPromise($this->current))->then(null, $onRejected) |
56
|
|
|
: new RejectedPromise($this->current); |
57
|
|
|
} |
58
|
|
|
|
59
|
|
|
public function setWaitCallback(\Closure $callback = null) |
60
|
|
|
{ |
61
|
|
|
if (!($callback instanceof \Closure)) { |
62
|
|
|
throw new \InvalidArgumentException( |
63
|
|
|
sprintf("Parameter 1 of %s must be a valid callback.", __METHOD__) |
64
|
|
|
); |
65
|
|
|
} |
66
|
|
|
|
67
|
|
|
$this->waitCallback = $callback; |
68
|
|
|
} |
69
|
|
|
|
70
|
|
|
public function setCancelCallback(\Closure $callback = null) |
71
|
|
|
{ |
72
|
|
|
if (!($callback instanceof \Closure)) { |
73
|
|
|
throw new \InvalidArgumentException( |
74
|
|
|
sprintf("Parameter 1 of %s must be a valid callback.", __METHOD__) |
75
|
|
|
); |
76
|
|
|
} |
77
|
|
|
|
78
|
|
|
$this->cancelCallback = $callback; |
79
|
|
|
} |
80
|
|
|
|
81
|
|
|
private function validateState($value, $state) |
82
|
|
|
{ |
83
|
|
|
if ($this->state !== self::STATE_PENDING) { |
84
|
|
|
if ($value === $this->current && $state === $this->state) { |
85
|
|
|
return false; |
86
|
|
|
} |
87
|
|
|
|
88
|
|
|
$prevStatus = $this->state; |
89
|
|
|
$currentStatus = $state; |
90
|
|
|
|
91
|
|
|
throw $this->state === $state |
92
|
|
|
? new \LogicException( |
93
|
|
|
sprintf("State of the promise is already %s", $prevStatus == 4 ? 'fulfilled' : 'rejected') |
94
|
|
|
) |
95
|
|
|
: new \LogicException( |
96
|
|
|
sprintf( |
97
|
|
|
"Unable to change %s promise to %s", |
98
|
|
|
$prevStatus == 4 ? 'fulfilled' : 'rejected', |
99
|
|
|
$currentStatus == 4 ? 'fulfilled' : 'rejected' |
100
|
|
|
) |
101
|
|
|
); |
102
|
|
|
|
103
|
|
|
return false; |
|
|
|
|
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
if ($value === $this) { |
107
|
|
|
throw new \LogicException( |
108
|
|
|
"Unable to fulfill or reject a promise with itself." |
109
|
|
|
); |
110
|
|
|
|
111
|
|
|
return false; |
|
|
|
|
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
return true; |
115
|
|
|
} |
116
|
|
|
|
117
|
|
|
public function resolve($value) |
118
|
|
|
{ |
119
|
|
|
$this->isStateTransformed = $this->validateState($value, self::STATE_FULFILLED); |
120
|
|
|
|
121
|
|
|
if ($this->isStateTransformed) { |
122
|
|
|
$this->setState(self::STATE_FULFILLED); |
123
|
|
|
$this->trigger($value); |
124
|
|
|
} |
125
|
|
|
} |
126
|
|
|
|
127
|
|
|
public function reject($reason) |
128
|
|
|
{ |
129
|
|
|
$this->isStateTransformed = $this->validateState($reason, self::STATE_REJECTED); |
130
|
|
|
|
131
|
|
|
if ($this->isStateTransformed) { |
132
|
|
|
$this->setState(self::STATE_REJECTED); |
133
|
|
|
$this->trigger($reason); |
134
|
|
|
} |
135
|
|
|
} |
136
|
|
|
|
137
|
|
|
public function wait($callback = null) |
138
|
|
|
{ |
139
|
|
|
if ($this->waitCallback === null) { |
140
|
|
|
if (!($callback instanceof \Closure)) { |
141
|
|
|
throw new \LogicException( |
142
|
|
|
sprintf( |
143
|
|
|
"Default waiting callback resolver is not set. " . |
144
|
|
|
"Synchronous wait mechanism is impossible to invoked because %s is called " . |
145
|
|
|
"without callback resolver.", __METHOD__ |
146
|
|
|
) |
147
|
|
|
); |
148
|
|
|
} |
149
|
|
|
|
150
|
|
|
$this->waitCallback = $callback; |
151
|
|
|
} |
152
|
|
|
|
153
|
|
|
if ($this->currentState() !== self::STATE_PENDING) { |
154
|
|
|
return; |
155
|
|
|
} |
156
|
|
|
|
157
|
|
|
try { |
158
|
|
|
$waitCallback = $this->waitCallback; |
159
|
|
|
$this->waitCallback = null; |
160
|
|
|
$waitCallback(); |
161
|
|
|
} catch (\Exception $e) { |
162
|
|
|
if ($this->currentState() === self::STATE_PENDING) { |
163
|
|
|
$this->reject($e); |
164
|
|
|
} else { |
165
|
|
|
throw $e; |
166
|
|
|
} |
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
Context\ContextStack::getQueueHandler()->run(); |
|
|
|
|
170
|
|
|
|
171
|
|
|
if ($this->currentState() === self::STATE_PENDING) { |
172
|
|
|
$this->reject( |
173
|
|
|
'Invoking the synchronous wait callback resolver didn\'t resolve the current promise' |
174
|
|
|
); |
175
|
|
|
} |
176
|
|
|
|
177
|
|
|
$q = $this->current instanceof PromiseInterface |
178
|
|
|
? $this->current->wait() |
|
|
|
|
179
|
|
|
: $this->current; |
180
|
|
|
|
181
|
|
|
if ($this->current instanceof PromiseInterface || $this->currentState() === self::STATE_FULFILLED) { |
182
|
|
|
return $q; |
183
|
|
|
} |
184
|
|
|
} |
185
|
|
|
|
186
|
|
|
public function cancel($callback = null) |
187
|
|
|
{ |
188
|
|
|
if ($this->cancelCallback === null) { |
189
|
|
|
if (!($callback instanceof \Closure)) { |
190
|
|
|
throw new \LogicException( |
191
|
|
|
sprintf( |
192
|
|
|
"Default cancellation callback resolver is not set. " . |
193
|
|
|
"Cancellation mechanism is impossible to invoked because %s is called " . |
194
|
|
|
"without callback resolver.", __METHOD__ |
195
|
|
|
) |
196
|
|
|
); |
197
|
|
|
} |
198
|
|
|
|
199
|
|
|
$this->cancelCallback = $callback; |
200
|
|
|
} |
201
|
|
|
|
202
|
|
|
if ($this->currentState() !== self::STATE_PENDING) { |
203
|
|
|
return; |
204
|
|
|
} |
205
|
|
|
|
206
|
|
|
if ($this->cancelCallback) { |
207
|
|
|
$cancelCallback = $this->cancelCallback; |
208
|
|
|
$this->cancelCallback = null; |
209
|
|
|
|
210
|
|
|
try { |
211
|
|
|
$cancelCallback(); |
212
|
|
|
} catch (\Exception $e) { |
213
|
|
|
$this->reject($e); |
214
|
|
|
} |
215
|
|
|
} |
216
|
|
|
|
217
|
|
|
if ($this->currentState() === self::STATE_PENDING) { |
218
|
|
|
$this->reject(new \Exception('Promise has been cancelled.')); |
219
|
|
|
} |
220
|
|
|
|
221
|
|
|
$e = $this->current instanceof PromiseInterface |
222
|
|
|
? $this->current->cancel() |
|
|
|
|
223
|
|
|
: $this->current; |
224
|
|
|
|
225
|
|
|
if ($this->current instanceof PromiseInterface || $this->currentState() === self::STATE_REJECTED) { |
226
|
|
|
return $e; |
227
|
|
|
} |
228
|
|
|
} |
229
|
|
|
|
230
|
|
|
public function currentState() |
231
|
|
|
{ |
232
|
|
|
return $this->state; |
233
|
|
|
} |
234
|
|
|
|
235
|
|
|
private function trigger($value) |
236
|
|
|
{ |
237
|
|
|
if ($this->state === self::STATE_PENDING) { |
238
|
|
|
throw new \LogicException( |
239
|
|
|
"Cannot resolving or rejecting promise when in pending state." |
240
|
|
|
); |
241
|
|
|
} |
242
|
|
|
|
243
|
|
|
$context = $this->context; |
244
|
|
|
$this->context = null; |
|
|
|
|
245
|
|
|
$this->current = $value; |
246
|
|
|
|
247
|
|
|
if (!method_exists($value, 'then')) { |
248
|
|
|
$index = $this->state === self::STATE_FULFILLED ? 1 : 2; |
249
|
|
|
|
250
|
|
|
Context\ContextStack::create(new TaskQueue)->store( |
251
|
|
|
static function () use ($index, $context, $value) { |
252
|
|
|
foreach ($context as $c) { |
253
|
|
|
self::invokeContext($c, $index, $value); |
254
|
|
|
} |
255
|
|
|
}); |
256
|
|
|
|
257
|
|
|
Context\ContextStack::getQueueHandler()->run(); |
|
|
|
|
258
|
|
|
} elseif ($value instanceof Promise) { |
259
|
|
|
$value->context = array_merge($value->context, $context); |
260
|
|
|
} else { |
261
|
|
|
$value->then( |
262
|
|
|
static function ($value) use ($context) { |
263
|
|
|
foreach ($context as $c) { |
264
|
|
|
self::invokeContext($c, 1, $value); |
265
|
|
|
} |
266
|
|
|
}, |
267
|
|
|
static function ($reason) use ($context) { |
268
|
|
|
foreach ($context as $c) { |
269
|
|
|
self::invokeContext($c, 2, $reason); |
270
|
|
|
} |
271
|
|
|
} |
272
|
|
|
); |
273
|
|
|
} |
274
|
|
|
} |
275
|
|
|
|
276
|
|
|
private static function invokeContext($context, $index, $value) |
277
|
|
|
{ |
278
|
|
|
$promise = $context[0]; |
279
|
|
|
|
280
|
|
|
if ($promise->currentState() !== self::STATE_PENDING) { |
281
|
|
|
return; |
282
|
|
|
} |
283
|
|
|
|
284
|
|
|
try { |
285
|
|
|
if (isset($context[$index])) { |
286
|
|
|
$promise->resolve($context[$index]($value)); |
287
|
|
|
} elseif ($index === 1) { |
288
|
|
|
$promise->resolve($value); |
289
|
|
|
} else { |
290
|
|
|
$promise->reject($value); |
291
|
|
|
} |
292
|
|
|
} catch (\Exception $e) { |
293
|
|
|
$promise->reject($e); |
294
|
|
|
} |
295
|
|
|
} |
296
|
|
|
|
297
|
|
|
private function setState($state) |
298
|
|
|
{ |
299
|
|
|
if ($state !== self::STATE_PENDING && |
300
|
|
|
$state !== self::STATE_FULFILLED && |
301
|
|
|
$state !== self::STATE_REJECTED) { |
302
|
|
|
throw new \InvalidArgumentException( |
303
|
|
|
sprintf("Parameter 1 of %s must be a valid promise state.", __METHOD__) |
304
|
|
|
); |
305
|
|
|
} |
306
|
|
|
|
307
|
|
|
$this->state = $state; |
308
|
|
|
} |
309
|
|
|
} |
310
|
|
|
|
This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.
Unreachable code is most often the result of
return
,die
orexit
statements that have been added for debug purposes.In the above example, the last
return false
will never be executed, because a return statement has already been met in every possible execution path.