PromiseTest.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593
  1. <?php
  2. namespace GuzzleHttp\Promise\Tests;
  3. use GuzzleHttp\Promise\CancellationException;
  4. use GuzzleHttp\Promise as P;
  5. use GuzzleHttp\Promise\FulfilledPromise;
  6. use GuzzleHttp\Promise\Promise;
  7. use GuzzleHttp\Promise\RejectedPromise;
  8. use GuzzleHttp\Promise\RejectionException;
  9. use PHPUnit\Framework\TestCase;
  10. /**
  11. * @covers GuzzleHttp\Promise\Promise
  12. */
  13. class PromiseTest extends TestCase
  14. {
  15. /**
  16. * @expectedException \LogicException
  17. * @expectedExceptionMessage The promise is already fulfilled
  18. */
  19. public function testCannotResolveNonPendingPromise()
  20. {
  21. $p = new Promise();
  22. $p->resolve('foo');
  23. $p->resolve('bar');
  24. $this->assertEquals('foo', $p->wait());
  25. }
  26. public function testCanResolveWithSameValue()
  27. {
  28. $p = new Promise();
  29. $p->resolve('foo');
  30. $p->resolve('foo');
  31. }
  32. /**
  33. * @expectedException \LogicException
  34. * @expectedExceptionMessage Cannot change a fulfilled promise to rejected
  35. */
  36. public function testCannotRejectNonPendingPromise()
  37. {
  38. $p = new Promise();
  39. $p->resolve('foo');
  40. $p->reject('bar');
  41. $this->assertEquals('foo', $p->wait());
  42. }
  43. public function testCanRejectWithSameValue()
  44. {
  45. $p = new Promise();
  46. $p->reject('foo');
  47. $p->reject('foo');
  48. }
  49. /**
  50. * @expectedException \LogicException
  51. * @expectedExceptionMessage Cannot change a fulfilled promise to rejected
  52. */
  53. public function testCannotRejectResolveWithSameValue()
  54. {
  55. $p = new Promise();
  56. $p->resolve('foo');
  57. $p->reject('foo');
  58. }
  59. public function testInvokesWaitFunction()
  60. {
  61. $p = new Promise(function () use (&$p) { $p->resolve('10'); });
  62. $this->assertEquals('10', $p->wait());
  63. }
  64. /**
  65. * @expectedException \GuzzleHttp\Promise\RejectionException
  66. */
  67. public function testRejectsAndThrowsWhenWaitFailsToResolve()
  68. {
  69. $p = new Promise(function () {});
  70. $p->wait();
  71. }
  72. /**
  73. * @expectedException \GuzzleHttp\Promise\RejectionException
  74. * @expectedExceptionMessage The promise was rejected with reason: foo
  75. */
  76. public function testThrowsWhenUnwrapIsRejectedWithNonException()
  77. {
  78. $p = new Promise(function () use (&$p) { $p->reject('foo'); });
  79. $p->wait();
  80. }
  81. /**
  82. * @expectedException \UnexpectedValueException
  83. * @expectedExceptionMessage foo
  84. */
  85. public function testThrowsWhenUnwrapIsRejectedWithException()
  86. {
  87. $e = new \UnexpectedValueException('foo');
  88. $p = new Promise(function () use (&$p, $e) { $p->reject($e); });
  89. $p->wait();
  90. }
  91. public function testDoesNotUnwrapExceptionsWhenDisabled()
  92. {
  93. $p = new Promise(function () use (&$p) { $p->reject('foo'); });
  94. $this->assertEquals('pending', $p->getState());
  95. $p->wait(false);
  96. $this->assertEquals('rejected', $p->getState());
  97. }
  98. public function testRejectsSelfWhenWaitThrows()
  99. {
  100. $e = new \UnexpectedValueException('foo');
  101. $p = new Promise(function () use ($e) { throw $e; });
  102. try {
  103. $p->wait();
  104. $this->fail();
  105. } catch (\UnexpectedValueException $e) {
  106. $this->assertEquals('rejected', $p->getState());
  107. }
  108. }
  109. public function testWaitsOnNestedPromises()
  110. {
  111. $p = new Promise(function () use (&$p) { $p->resolve('_'); });
  112. $p2 = new Promise(function () use (&$p2) { $p2->resolve('foo'); });
  113. $p3 = $p->then(function () use ($p2) { return $p2; });
  114. $this->assertSame('foo', $p3->wait());
  115. }
  116. /**
  117. * @expectedException \GuzzleHttp\Promise\RejectionException
  118. */
  119. public function testThrowsWhenWaitingOnPromiseWithNoWaitFunction()
  120. {
  121. $p = new Promise();
  122. $p->wait();
  123. }
  124. public function testThrowsWaitExceptionAfterPromiseIsResolved()
  125. {
  126. $p = new Promise(function () use (&$p) {
  127. $p->reject('Foo!');
  128. throw new \Exception('Bar?');
  129. });
  130. try {
  131. $p->wait();
  132. $this->fail();
  133. } catch (\Exception $e) {
  134. $this->assertEquals('Bar?', $e->getMessage());
  135. }
  136. }
  137. public function testGetsActualWaitValueFromThen()
  138. {
  139. $p = new Promise(function () use (&$p) { $p->reject('Foo!'); });
  140. $p2 = $p->then(null, function ($reason) {
  141. return new RejectedPromise([$reason]);
  142. });
  143. try {
  144. $p2->wait();
  145. $this->fail('Should have thrown');
  146. } catch (RejectionException $e) {
  147. $this->assertEquals(['Foo!'], $e->getReason());
  148. }
  149. }
  150. public function testWaitBehaviorIsBasedOnLastPromiseInChain()
  151. {
  152. $p3 = new Promise(function () use (&$p3) { $p3->resolve('Whoop'); });
  153. $p2 = new Promise(function () use (&$p2, $p3) { $p2->reject($p3); });
  154. $p = new Promise(function () use (&$p, $p2) { $p->reject($p2); });
  155. $this->assertEquals('Whoop', $p->wait());
  156. }
  157. public function testWaitsOnAPromiseChainEvenWhenNotUnwrapped()
  158. {
  159. $p2 = new Promise(function () use (&$p2) {
  160. $p2->reject('Fail');
  161. });
  162. $p = new Promise(function () use ($p2, &$p) {
  163. $p->resolve($p2);
  164. });
  165. $p->wait(false);
  166. $this->assertSame(Promise::REJECTED, $p2->getState());
  167. }
  168. public function testCannotCancelNonPending()
  169. {
  170. $p = new Promise();
  171. $p->resolve('foo');
  172. $p->cancel();
  173. $this->assertEquals('fulfilled', $p->getState());
  174. }
  175. /**
  176. * @expectedException \GuzzleHttp\Promise\CancellationException
  177. */
  178. public function testCancelsPromiseWhenNoCancelFunction()
  179. {
  180. $p = new Promise();
  181. $p->cancel();
  182. $this->assertEquals('rejected', $p->getState());
  183. $p->wait();
  184. }
  185. public function testCancelsPromiseWithCancelFunction()
  186. {
  187. $called = false;
  188. $p = new Promise(null, function () use (&$called) { $called = true; });
  189. $p->cancel();
  190. $this->assertEquals('rejected', $p->getState());
  191. $this->assertTrue($called);
  192. }
  193. public function testCancelsUppermostPendingPromise()
  194. {
  195. $called = false;
  196. $p1 = new Promise(null, function () use (&$called) { $called = true; });
  197. $p2 = $p1->then(function () {});
  198. $p3 = $p2->then(function () {});
  199. $p4 = $p3->then(function () {});
  200. $p3->cancel();
  201. $this->assertEquals('rejected', $p1->getState());
  202. $this->assertEquals('rejected', $p2->getState());
  203. $this->assertEquals('rejected', $p3->getState());
  204. $this->assertEquals('pending', $p4->getState());
  205. $this->assertTrue($called);
  206. try {
  207. $p3->wait();
  208. $this->fail();
  209. } catch (CancellationException $e) {
  210. $this->assertContains('cancelled', $e->getMessage());
  211. }
  212. try {
  213. $p4->wait();
  214. $this->fail();
  215. } catch (CancellationException $e) {
  216. $this->assertContains('cancelled', $e->getMessage());
  217. }
  218. $this->assertEquals('rejected', $p4->getState());
  219. }
  220. public function testCancelsChildPromises()
  221. {
  222. $called1 = $called2 = $called3 = false;
  223. $p1 = new Promise(null, function () use (&$called1) { $called1 = true; });
  224. $p2 = new Promise(null, function () use (&$called2) { $called2 = true; });
  225. $p3 = new Promise(null, function () use (&$called3) { $called3 = true; });
  226. $p4 = $p2->then(function () use ($p3) { return $p3; });
  227. $p5 = $p4->then(function () { $this->fail(); });
  228. $p4->cancel();
  229. $this->assertEquals('pending', $p1->getState());
  230. $this->assertEquals('rejected', $p2->getState());
  231. $this->assertEquals('rejected', $p4->getState());
  232. $this->assertEquals('pending', $p5->getState());
  233. $this->assertFalse($called1);
  234. $this->assertTrue($called2);
  235. $this->assertFalse($called3);
  236. }
  237. public function testRejectsPromiseWhenCancelFails()
  238. {
  239. $called = false;
  240. $p = new Promise(null, function () use (&$called) {
  241. $called = true;
  242. throw new \Exception('e');
  243. });
  244. $p->cancel();
  245. $this->assertEquals('rejected', $p->getState());
  246. $this->assertTrue($called);
  247. try {
  248. $p->wait();
  249. $this->fail();
  250. } catch (\Exception $e) {
  251. $this->assertEquals('e', $e->getMessage());
  252. }
  253. }
  254. public function testCreatesPromiseWhenFulfilledAfterThen()
  255. {
  256. $p = new Promise();
  257. $carry = null;
  258. $p2 = $p->then(function ($v) use (&$carry) { $carry = $v; });
  259. $this->assertNotSame($p, $p2);
  260. $p->resolve('foo');
  261. P\queue()->run();
  262. $this->assertEquals('foo', $carry);
  263. }
  264. public function testCreatesPromiseWhenFulfilledBeforeThen()
  265. {
  266. $p = new Promise();
  267. $p->resolve('foo');
  268. $carry = null;
  269. $p2 = $p->then(function ($v) use (&$carry) { $carry = $v; });
  270. $this->assertNotSame($p, $p2);
  271. $this->assertNull($carry);
  272. \GuzzleHttp\Promise\queue()->run();
  273. $this->assertEquals('foo', $carry);
  274. }
  275. public function testCreatesPromiseWhenFulfilledWithNoCallback()
  276. {
  277. $p = new Promise();
  278. $p->resolve('foo');
  279. $p2 = $p->then();
  280. $this->assertNotSame($p, $p2);
  281. $this->assertInstanceOf(FulfilledPromise::class, $p2);
  282. }
  283. public function testCreatesPromiseWhenRejectedAfterThen()
  284. {
  285. $p = new Promise();
  286. $carry = null;
  287. $p2 = $p->then(null, function ($v) use (&$carry) { $carry = $v; });
  288. $this->assertNotSame($p, $p2);
  289. $p->reject('foo');
  290. P\queue()->run();
  291. $this->assertEquals('foo', $carry);
  292. }
  293. public function testCreatesPromiseWhenRejectedBeforeThen()
  294. {
  295. $p = new Promise();
  296. $p->reject('foo');
  297. $carry = null;
  298. $p2 = $p->then(null, function ($v) use (&$carry) { $carry = $v; });
  299. $this->assertNotSame($p, $p2);
  300. $this->assertNull($carry);
  301. P\queue()->run();
  302. $this->assertEquals('foo', $carry);
  303. }
  304. public function testCreatesPromiseWhenRejectedWithNoCallback()
  305. {
  306. $p = new Promise();
  307. $p->reject('foo');
  308. $p2 = $p->then();
  309. $this->assertNotSame($p, $p2);
  310. $this->assertInstanceOf(RejectedPromise::class, $p2);
  311. }
  312. public function testInvokesWaitFnsForThens()
  313. {
  314. $p = new Promise(function () use (&$p) { $p->resolve('a'); });
  315. $p2 = $p
  316. ->then(function ($v) { return $v . '-1-'; })
  317. ->then(function ($v) { return $v . '2'; });
  318. $this->assertEquals('a-1-2', $p2->wait());
  319. }
  320. public function testStacksThenWaitFunctions()
  321. {
  322. $p1 = new Promise(function () use (&$p1) { $p1->resolve('a'); });
  323. $p2 = new Promise(function () use (&$p2) { $p2->resolve('b'); });
  324. $p3 = new Promise(function () use (&$p3) { $p3->resolve('c'); });
  325. $p4 = $p1
  326. ->then(function () use ($p2) { return $p2; })
  327. ->then(function () use ($p3) { return $p3; });
  328. $this->assertEquals('c', $p4->wait());
  329. }
  330. public function testForwardsFulfilledDownChainBetweenGaps()
  331. {
  332. $p = new Promise();
  333. $r = $r2 = null;
  334. $p->then(null, null)
  335. ->then(function ($v) use (&$r) { $r = $v; return $v . '2'; })
  336. ->then(function ($v) use (&$r2) { $r2 = $v; });
  337. $p->resolve('foo');
  338. P\queue()->run();
  339. $this->assertEquals('foo', $r);
  340. $this->assertEquals('foo2', $r2);
  341. }
  342. public function testForwardsRejectedPromisesDownChainBetweenGaps()
  343. {
  344. $p = new Promise();
  345. $r = $r2 = null;
  346. $p->then(null, null)
  347. ->then(null, function ($v) use (&$r) { $r = $v; return $v . '2'; })
  348. ->then(function ($v) use (&$r2) { $r2 = $v; });
  349. $p->reject('foo');
  350. P\queue()->run();
  351. $this->assertEquals('foo', $r);
  352. $this->assertEquals('foo2', $r2);
  353. }
  354. public function testForwardsThrownPromisesDownChainBetweenGaps()
  355. {
  356. $e = new \Exception();
  357. $p = new Promise();
  358. $r = $r2 = null;
  359. $p->then(null, null)
  360. ->then(null, function ($v) use (&$r, $e) {
  361. $r = $v;
  362. throw $e;
  363. })
  364. ->then(
  365. null,
  366. function ($v) use (&$r2) { $r2 = $v; }
  367. );
  368. $p->reject('foo');
  369. P\queue()->run();
  370. $this->assertEquals('foo', $r);
  371. $this->assertSame($e, $r2);
  372. }
  373. public function testForwardsReturnedRejectedPromisesDownChainBetweenGaps()
  374. {
  375. $p = new Promise();
  376. $rejected = new RejectedPromise('bar');
  377. $r = $r2 = null;
  378. $p->then(null, null)
  379. ->then(null, function ($v) use (&$r, $rejected) {
  380. $r = $v;
  381. return $rejected;
  382. })
  383. ->then(
  384. null,
  385. function ($v) use (&$r2) { $r2 = $v; }
  386. );
  387. $p->reject('foo');
  388. P\queue()->run();
  389. $this->assertEquals('foo', $r);
  390. $this->assertEquals('bar', $r2);
  391. try {
  392. $p->wait();
  393. } catch (RejectionException $e) {
  394. $this->assertEquals('foo', $e->getReason());
  395. }
  396. }
  397. public function testForwardsHandlersToNextPromise()
  398. {
  399. $p = new Promise();
  400. $p2 = new Promise();
  401. $resolved = null;
  402. $p
  403. ->then(function ($v) use ($p2) { return $p2; })
  404. ->then(function ($value) use (&$resolved) { $resolved = $value; });
  405. $p->resolve('a');
  406. $p2->resolve('b');
  407. P\queue()->run();
  408. $this->assertEquals('b', $resolved);
  409. }
  410. public function testRemovesReferenceFromChildWhenParentWaitedUpon()
  411. {
  412. $r = null;
  413. $p = new Promise(function () use (&$p) { $p->resolve('a'); });
  414. $p2 = new Promise(function () use (&$p2) { $p2->resolve('b'); });
  415. $pb = $p->then(
  416. function ($v) use ($p2, &$r) {
  417. $r = $v;
  418. return $p2;
  419. })
  420. ->then(function ($v) { return $v . '.'; });
  421. $this->assertEquals('a', $p->wait());
  422. $this->assertEquals('b', $p2->wait());
  423. $this->assertEquals('b.', $pb->wait());
  424. $this->assertEquals('a', $r);
  425. }
  426. public function testForwardsHandlersWhenFulfilledPromiseIsReturned()
  427. {
  428. $res = [];
  429. $p = new Promise();
  430. $p2 = new Promise();
  431. $p2->resolve('foo');
  432. $p2->then(function ($v) use (&$res) { $res[] = 'A:' . $v; });
  433. // $res is A:foo
  434. $p
  435. ->then(function () use ($p2, &$res) { $res[] = 'B'; return $p2; })
  436. ->then(function ($v) use (&$res) { $res[] = 'C:' . $v; });
  437. $p->resolve('a');
  438. $p->then(function ($v) use (&$res) { $res[] = 'D:' . $v; });
  439. P\queue()->run();
  440. $this->assertEquals(['A:foo', 'B', 'D:a', 'C:foo'], $res);
  441. }
  442. public function testForwardsHandlersWhenRejectedPromiseIsReturned()
  443. {
  444. $res = [];
  445. $p = new Promise();
  446. $p2 = new Promise();
  447. $p2->reject('foo');
  448. $p2->then(null, function ($v) use (&$res) { $res[] = 'A:' . $v; });
  449. $p->then(null, function () use ($p2, &$res) { $res[] = 'B'; return $p2; })
  450. ->then(null, function ($v) use (&$res) { $res[] = 'C:' . $v; });
  451. $p->reject('a');
  452. $p->then(null, function ($v) use (&$res) { $res[] = 'D:' . $v; });
  453. P\queue()->run();
  454. $this->assertEquals(['A:foo', 'B', 'D:a', 'C:foo'], $res);
  455. }
  456. public function testDoesNotForwardRejectedPromise()
  457. {
  458. $res = [];
  459. $p = new Promise();
  460. $p2 = new Promise();
  461. $p2->cancel();
  462. $p2->then(function ($v) use (&$res) { $res[] = "B:$v"; return $v; });
  463. $p->then(function ($v) use ($p2, &$res) { $res[] = "B:$v"; return $p2; })
  464. ->then(function ($v) use (&$res) { $res[] = 'C:' . $v; });
  465. $p->resolve('a');
  466. $p->then(function ($v) use (&$res) { $res[] = 'D:' . $v; });
  467. P\queue()->run();
  468. $this->assertEquals(['B:a', 'D:a'], $res);
  469. }
  470. public function testRecursivelyForwardsWhenOnlyThennable()
  471. {
  472. $res = [];
  473. $p = new Promise();
  474. $p2 = new Thennable();
  475. $p2->resolve('foo');
  476. $p2->then(function ($v) use (&$res) { $res[] = 'A:' . $v; });
  477. $p->then(function () use ($p2, &$res) { $res[] = 'B'; return $p2; })
  478. ->then(function ($v) use (&$res) { $res[] = 'C:' . $v; });
  479. $p->resolve('a');
  480. $p->then(function ($v) use (&$res) { $res[] = 'D:' . $v; });
  481. P\queue()->run();
  482. $this->assertEquals(['A:foo', 'B', 'D:a', 'C:foo'], $res);
  483. }
  484. public function testRecursivelyForwardsWhenNotInstanceOfPromise()
  485. {
  486. $res = [];
  487. $p = new Promise();
  488. $p2 = new NotPromiseInstance();
  489. $p2->then(function ($v) use (&$res) { $res[] = 'A:' . $v; });
  490. $p->then(function () use ($p2, &$res) { $res[] = 'B'; return $p2; })
  491. ->then(function ($v) use (&$res) { $res[] = 'C:' . $v; });
  492. $p->resolve('a');
  493. $p->then(function ($v) use (&$res) { $res[] = 'D:' . $v; });
  494. P\queue()->run();
  495. $this->assertEquals(['B', 'D:a'], $res);
  496. $p2->resolve('foo');
  497. P\queue()->run();
  498. $this->assertEquals(['B', 'D:a', 'A:foo', 'C:foo'], $res);
  499. }
  500. /**
  501. * @expectedException \LogicException
  502. * @expectedExceptionMessage Cannot fulfill or reject a promise with itself
  503. */
  504. public function testCannotResolveWithSelf()
  505. {
  506. $p = new Promise();
  507. $p->resolve($p);
  508. }
  509. /**
  510. * @expectedException \LogicException
  511. * @expectedExceptionMessage Cannot fulfill or reject a promise with itself
  512. */
  513. public function testCannotRejectWithSelf()
  514. {
  515. $p = new Promise();
  516. $p->reject($p);
  517. }
  518. public function testDoesNotBlowStackWhenWaitingOnNestedThens()
  519. {
  520. $inner = new Promise(function () use (&$inner) { $inner->resolve(0); });
  521. $prev = $inner;
  522. for ($i = 1; $i < 100; $i++) {
  523. $prev = $prev->then(function ($i) { return $i + 1; });
  524. }
  525. $parent = new Promise(function () use (&$parent, $prev) {
  526. $parent->resolve($prev);
  527. });
  528. $this->assertEquals(99, $parent->wait());
  529. }
  530. public function testOtherwiseIsSugarForRejections()
  531. {
  532. $p = new Promise();
  533. $p->reject('foo');
  534. $p->otherwise(function ($v) use (&$c) { $c = $v; });
  535. P\queue()->run();
  536. $this->assertEquals($c, 'foo');
  537. }
  538. }