@@ -9,16 +9,23 @@ and [Promises/A+](http://promises-aplus.github.io/promises-spec/) specifications
9
9
Promises are similar to futures and share many of the same behaviours. Promises are far more robust,
10
10
however. Promises can be chained in a tree structure where each promise may have zero or more children.
11
11
Promises are chained using the ` then ` method. The result of a call to ` then ` is always another promise.
12
- Promises are resolved asynchronously in the order they are added to the tree. Parents are guaranteed
13
- to be resolved before their children. The result of each promise is passed to each of its children
14
- upon resolution. When a promise is rejected all its children will be summarily rejected.
15
-
16
- Promises have three possible states: * pending* , * rejected* , and * fulfilled* . When a promise is created it is set
17
- to * pending* and will remain in that state until processing is complete. A completed promise is either * rejected* ,
18
- indicating that an exception was thrown during processing, or * fulfilled* , indicating it succeeded. If a promise is
19
- * fulfilled* its ` value ` will be updated to reflect the result of the operation. If * rejected* the ` reason ` will
20
- be updated with a reference to the thrown exception. The predicate methods ` pending? ` , ` rejected ` , and ` fulfilled? `
12
+ Promises are resolved asynchronously: parents are guaranteed to be resolved before their children.
13
+ ` then ` takes two parameters: an optional block to be executed upon parent resolution and an optional callable
14
+ to be executed upon parent failure.
15
+ The result of each promise is passed to each of its children upon resolution.
16
+ When a promise is rejected all its children will receive the reason.
17
+
18
+ Promises have four possible states: * unscheduled* , * pending* , * rejected* , and * fulfilled* .
19
+ Promise created using ` .new ` will be * unscheduled* , to schedule it ` execute ` method must be called and the
20
+ Promise with all its children will be set to * pending* .
21
+ When a promise is * pending* will remain in that state until processing is complete.
22
+ A completed promise is either * rejected* , indicating that an exception was thrown during processing, or * fulfilled* , indicating it succeeded.
23
+ If a promise is * fulfilled* its ` value ` will be updated to reflect the result of the operation.
24
+ If * rejected* the ` reason ` will be updated with a reference to the thrown exception.
25
+ The predicate methods ` unscheduled? ` , ` pending? ` , ` rejected ` , and ` fulfilled? `
21
26
can be called at any time to obtain the state of the promise, as can the ` state ` method, which returns a symbol.
27
+ Promise created using ` .execute ` will be * pending* , a Promise created using ` .fulfill(value) ` will be * fulfilled*
28
+ with the passed value and a Promise created using ` .reject(reason) ` will be * rejected* .
22
29
23
30
Retrieving the value of a promise is done through the ` value ` (alias: ` deref ` ) method. Obtaining the value of
24
31
a promise is a potentially blocking operation. When a promise is * rejected* a call to ` value ` will return ` nil `
@@ -41,40 +48,47 @@ require 'concurrent'
41
48
Then create one
42
49
43
50
``` ruby
44
- p = Promise .new (" Jerry" , " D'Antonio" ) do |first , last |
45
- " #{ last } , #{ first } "
51
+ p = Promise .execute do
52
+ # do something
53
+ 42
46
54
end
47
55
```
48
56
49
57
Promises can be chained using the ` then ` method. The ` then ` method
50
- accepts a block but no arguments. The result of the each promise is
51
- passed as the block argument to chained promises
58
+ accepts a block, to be executed on fulfillment, and a callable argument to be executed on rejection.
59
+ The result of the each promise is passed as the block argument to chained promises.
52
60
53
61
``` ruby
54
- p = Concurrent ::Promise .new ( 10 ) {|x | x * 2 }.then{|result | result - 10 }
62
+ p = Concurrent ::Promise .new { 10 }.then {|x | x * 2 }.then{|result | result - 10 }
55
63
```
56
64
57
65
And so on, and so on, and so on...
58
66
59
67
``` ruby
60
- p = Concurrent ::Promise .new ( 10 ){| x | x * 2 } .
68
+ p = Concurrent ::Promise .fulfill( 20 ) .
61
69
then {|result | result - 10 }.
62
70
then {|result | result * 3 }.
63
71
then {|result | result % 5 }
64
72
```
65
73
66
- Promises are executed asynchronously so a newly-created promise * should* always be in the pending state
74
+ Newly created promise state depends on the parent's one:
75
+ - if parent is * unscheduled* the child will be * unscheduled*
76
+ - if parent is * pending* the child will be * pending*
77
+ - if parent is * fulfilled* the child will be * pending*
78
+ - if parent is * rejected* the child will be * pending*
79
+ Promises are executed asynchronously so a newly-created promise could be in a different state right after
80
+ ` then ` returns, for example a child of a * rejected* parent could be already * fulfilled* or * rejected*
67
81
68
82
``` ruby
69
- p = Concurrent ::Promise .new { " Hello, world!" }
83
+ p = Concurrent ::Promise .execute { " Hello, world!" }
70
84
p .state # => :pending
71
85
p .pending? # => true
72
86
```
73
87
74
88
Wait a little bit, and the promise will resolve and provide a value
75
89
76
90
``` ruby
77
- p = Concurrent ::Promise .new { " Hello, world!" }
91
+ p = Concurrent ::Promise .execute { " Hello, world!" }
78
92
sleep (0.1 )
79
93
80
94
p .state # => :fulfilled
@@ -86,7 +100,7 @@ If an exception occurs, the promise will be rejected and will provide
86
100
a reason for the rejection
87
101
88
102
``` ruby
89
- p = Concurrent ::Promise .new { raise StandardError .new (" Here comes the Boom!" ) }
103
+ p = Concurrent ::Promise .execute { raise StandardError .new (" Here comes the Boom!" ) }
90
104
sleep (0.1 )
91
105
92
106
p .state # => :rejected
@@ -96,96 +110,34 @@ p.reason #=> "#<StandardError: Here comes the Boom!>"
96
110
97
111
### Rejection
98
112
99
- Much like the economy, rejection exhibits a trickle-down effect. When
100
- a promise is rejected all its children will be rejected
113
+ When a promise is rejected all its children will be receive the reason as the rejection callable parameter.
101
114
102
115
``` ruby
103
- p = [ Concurrent ::Promise .new { Thread .pass; raise StandardError } ]
116
+ p = [ Concurrent ::Promise .execute { Thread .pass; raise StandardError } ]
104
117
105
- 10 .times{|i | p << p .first.then{ i } }
106
- sleep (0.1 )
107
-
108
- p .length # => 11
109
- p .first.state # => :rejected
110
- p .last.state # => :rejected
111
- ```
118
+ c1 = p .then(Proc .new { |reason | 42 })
119
+ c2 = p .then(Proc .new { |reason | raise ' Boom!' })
112
120
113
- Once a promise is rejected it will not accept any children. Calls
114
- to ` then ` will continually return ` self `
115
121
116
- ``` ruby
117
- p = Concurrent ::Promise .new { raise StandardError }
118
122
sleep (0.1 )
119
123
120
- p .object_id # => 32960556
121
- p .then{}.object_id # => 32960556
122
- p .then{}.object_id # => 32960556
123
- ```
124
-
125
- ### Error Handling
126
-
127
- Promises support error handling callbacks is a style mimicing Ruby's
128
- own exception handling mechanism, namely ` rescue `
129
-
130
- ``` ruby
131
- Concurrent ::Promise .new { " dangerous operation..." }.rescue{|ex | puts " Bam!" }
132
-
133
- # -or- (for the Java/C# crowd)
134
- Concurrent ::Promise .new { " dangerous operation..." }.catch {|ex | puts " Boom!" }
135
-
136
- # -or- (for the hipsters)
137
- Concurrent ::Promise .new { " dangerous operation..." }.on_error{|ex | puts " Pow!" }
138
- ```
139
-
140
- As with Ruby's ` rescue ` mechanism, a promise's ` rescue ` method can
141
- accept an optional Exception class argument (defaults to ` Exception `
142
- when not specified)
143
-
144
- ``` ruby
145
- Concurrent ::Promise .new { " dangerous operation..." }.rescue(ArgumentError ){|ex | puts " Bam!" }
124
+ c1.state # => :fulfilled
125
+ c2.state # => :rejected
146
126
```
147
127
148
- Calls to ` rescue ` can also be chained
149
-
150
- ``` ruby
151
- Concurrent ::Promise .new { " dangerous operation..." }.
152
- rescue (ArgumentError ){|ex | puts " Bam!" }.
153
- rescue (NoMethodError ){|ex | puts " Boom!" }.
154
- rescue (StandardError ){|ex | puts " Pow!" }
155
- ```
156
-
157
- When there are multiple ` rescue ` handlers the first one to match the thrown
158
- exception will be triggered
159
-
160
- ``` ruby
161
- Concurrent ::Promise .new { raise NoMethodError }.
162
- rescue (ArgumentError ){|ex | puts " Bam!" }.
163
- rescue (NoMethodError ){|ex | puts " Boom!" }.
164
- rescue (StandardError ){|ex | puts " Pow!" }
165
-
166
- sleep (0.1 )
128
+ Once a promise is rejected it will continue to accept children that will receive immediately
129
+ rejection (they will be executed asynchronously)
167
130
168
- # => Boom!
169
- ```
131
+ ### Aliases
170
132
171
- Trickle-down rejection also applies to rescue handlers. When a promise is rejected,
172
- for any reason, its rescue handlers will be triggered. Rejection of the parent counts.
133
+ ` then ` method is the most generic one: it accepts a block to be executed upon parent fulfillment
134
+ and a callable to be executed upon parent rejection. At least one of them should be passed.
135
+ Default block is ` { |result| result } ` that fulfills the child with the parent value.
136
+ Default callable is ` { |reason| raise reason } ` that rejects the child with the parent reason.
173
137
174
- ``` ruby
175
- Concurrent ::Promise .new { Thread .pass; raise StandardError }.
176
- then { true }.rescue{ puts ' Boom!' }.
177
- then { true }.rescue{ puts ' Boom!' }.
178
- then { true }.rescue{ puts ' Boom!' }.
179
- then { true }.rescue{ puts ' Boom!' }.
180
- then { true }.rescue{ puts ' Boom!' }
181
- sleep (0.1 )
182
-
183
- # => Boom!
184
- # => Boom!
185
- # => Boom!
186
- # => Boom!
187
- # => Boom!
188
- ```
138
+ ` on_success { |result| ... } ` is the same as ` then {|result| ... } `
139
+ ` rescue { |reason| ... } ` is the same as ` then(Proc.new { |reason| ... } ) `
140
+ ` rescue ` is aliased by ` catch ` and ` on_error `
189
141
190
142
## Copyright
191
143
0 commit comments