Subversion Repositories cheapmusic

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
103 - 1
<?php
2
namespace GuzzleHttp\Promise;
3
 
4
use Exception;
5
use Generator;
6
use Throwable;
7
 
8
/**
9
 * Creates a promise that is resolved using a generator that yields values or
10
 * promises (somewhat similar to C#'s async keyword).
11
 *
12
 * When called, the coroutine function will start an instance of the generator
13
 * and returns a promise that is fulfilled with its final yielded value.
14
 *
15
 * Control is returned back to the generator when the yielded promise settles.
16
 * This can lead to less verbose code when doing lots of sequential async calls
17
 * with minimal processing in between.
18
 *
19
 *     use GuzzleHttp\Promise;
20
 *
21
 *     function createPromise($value) {
22
 *         return new Promise\FulfilledPromise($value);
23
 *     }
24
 *
25
 *     $promise = Promise\coroutine(function () {
26
 *         $value = (yield createPromise('a'));
27
 *         try {
28
 *             $value = (yield createPromise($value . 'b'));
29
 *         } catch (\Exception $e) {
30
 *             // The promise was rejected.
31
 *         }
32
 *         yield $value . 'c';
33
 *     });
34
 *
35
 *     // Outputs "abc"
36
 *     $promise->then(function ($v) { echo $v; });
37
 *
38
 * @param callable $generatorFn Generator function to wrap into a promise.
39
 *
40
 * @return Promise
41
 * @link https://github.com/petkaantonov/bluebird/blob/master/API.md#generators inspiration
42
 */
43
final class Coroutine implements PromiseInterface
44
{
45
    /**
46
     * @var PromiseInterface|null
47
     */
48
    private $currentPromise;
49
 
50
    /**
51
     * @var Generator
52
     */
53
    private $generator;
54
 
55
    /**
56
     * @var Promise
57
     */
58
    private $result;
59
 
60
    public function __construct(callable $generatorFn)
61
    {
62
        $this->generator = $generatorFn();
63
        $this->result = new Promise(function () {
64
            while (isset($this->currentPromise)) {
65
                $this->currentPromise->wait();
66
            }
67
        });
68
        $this->nextCoroutine($this->generator->current());
69
    }
70
 
71
    public function then(
72
        callable $onFulfilled = null,
73
        callable $onRejected = null
74
    ) {
75
        return $this->result->then($onFulfilled, $onRejected);
76
    }
77
 
78
    public function otherwise(callable $onRejected)
79
    {
80
        return $this->result->otherwise($onRejected);
81
    }
82
 
83
    public function wait($unwrap = true)
84
    {
85
        return $this->result->wait($unwrap);
86
    }
87
 
88
    public function getState()
89
    {
90
        return $this->result->getState();
91
    }
92
 
93
    public function resolve($value)
94
    {
95
        $this->result->resolve($value);
96
    }
97
 
98
    public function reject($reason)
99
    {
100
        $this->result->reject($reason);
101
    }
102
 
103
    public function cancel()
104
    {
105
        $this->currentPromise->cancel();
106
        $this->result->cancel();
107
    }
108
 
109
    private function nextCoroutine($yielded)
110
    {
111
        $this->currentPromise = promise_for($yielded)
112
            ->then([$this, '_handleSuccess'], [$this, '_handleFailure']);
113
    }
114
 
115
    /**
116
     * @internal
117
     */
118
    public function _handleSuccess($value)
119
    {
120
        unset($this->currentPromise);
121
        try {
122
            $next = $this->generator->send($value);
123
            if ($this->generator->valid()) {
124
                $this->nextCoroutine($next);
125
            } else {
126
                $this->result->resolve($value);
127
            }
128
        } catch (Exception $exception) {
129
            $this->result->reject($exception);
130
        } catch (Throwable $throwable) {
131
            $this->result->reject($throwable);
132
        }
133
    }
134
 
135
    /**
136
     * @internal
137
     */
138
    public function _handleFailure($reason)
139
    {
140
        unset($this->currentPromise);
141
        try {
142
            $nextYield = $this->generator->throw(exception_for($reason));
143
            // The throw was caught, so keep iterating on the coroutine
144
            $this->nextCoroutine($nextYield);
145
        } catch (Exception $exception) {
146
            $this->result->reject($exception);
147
        } catch (Throwable $throwable) {
148
            $this->result->reject($throwable);
149
        }
150
    }
151
}