Skip to content

Commit 7fe65a9

Browse files
committed
Added more documentation for the new Promise API.
1 parent e89ba53 commit 7fe65a9

File tree

1 file changed

+51
-31
lines changed

1 file changed

+51
-31
lines changed

md/promise.md

Lines changed: 51 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
# Promises, Promises...
22

3-
A promise is the most powerful and versatile of the concurrency objects in this library.
43
Promises are inspired by the JavaScript [Promises/A](http://wiki.commonjs.org/wiki/Promises/A)
54
and [Promises/A+](http://promises-aplus.github.io/promises-spec/) specifications.
65

@@ -9,23 +8,24 @@ and [Promises/A+](http://promises-aplus.github.io/promises-spec/) specifications
98
Promises are similar to futures and share many of the same behaviours. Promises are far more robust,
109
however. Promises can be chained in a tree structure where each promise may have zero or more children.
1110
Promises are chained using the `then` method. The result of a call to `then` is always another promise.
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.
11+
Promises are resolved asynchronously (with respect to the main thread) but in a strict order:
12+
parents are guaranteed to be resolved before their children, children before their younger siblings.
13+
The `then` method takes two parameters: an optional block to be executed upon parent resolution and an
14+
optional callable to be executed upon parent failure. The result of each promise is passed to each of its
15+
children upon resolution. When a promise is rejected all its children will be summarily rejected and will
16+
receive the reason.
1717

1818
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.
19+
A Promise created using `.new` will be *unscheduled*. It is scheduled by calling the `execute` method.
20+
Upon execution the Promise and all its children will be set to *pending*. When a promise is *pending* it will remain in that
21+
state until processing is complete. A completed Promise is either *rejected*, indicating that an exception
22+
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.
2424
If *rejected* the `reason` will be updated with a reference to the thrown exception.
25-
The predicate methods `unscheduled?`, `pending?`, `rejected`, and `fulfilled?`
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*.
25+
The predicate methods `unscheduled?`, `pending?`, `rejected?`, and `fulfilled?`
26+
can be called at any time to obtain the state of the Promise, as can the `state` method, which returns a symbol.
27+
A Promise created using `.execute` will be *pending*, a Promise created using `.fulfill(value)` will be *fulfilled*
28+
with the given value and a Promise created using `.reject(reason)` will be *rejected* with the given reason.
2929

3030
Retrieving the value of a promise is done through the `value` (alias: `deref`) method. Obtaining the value of
3131
a promise is a potentially blocking operation. When a promise is *rejected* a call to `value` will return `nil`
@@ -59,7 +59,7 @@ accepts a block, to be executed on fulfillment, and a callable argument to be ex
5959
The result of the each promise is passed as the block argument to chained promises.
6060

6161
```ruby
62-
p = Concurrent::Promise.new{10}.then{|x| x * 2}.then{|result| result - 10 }
62+
p = Concurrent::Promise.new{10}.then{|x| x * 2}.then{|result| result - 10 }.execute
6363
```
6464

6565
And so on, and so on, and so on...
@@ -68,24 +68,44 @@ And so on, and so on, and so on...
6868
p = Concurrent::Promise.fulfill(20).
6969
then{|result| result - 10 }.
7070
then{|result| result * 3 }.
71-
then{|result| result % 5 }
71+
then{|result| result % 5 }.execute
7272
```
7373

74-
Newly created promise state depends on the parent's one:
74+
The initial state of a newly created Promise depends on the state of its parent:
7575
- if parent is *unscheduled* the child will be *unscheduled*
7676
- if parent is *pending* the child will be *pending*
7777
- 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*
78+
- if parent is *rejected* the child will be *pending* (but will ultimately be *rejected*)
79+
80+
Promises are executed asynchronously from the main thread. By the time a child Promise finishes initialization
81+
it may be in a different state that its parent (by the time a child is created its parent may have completed
82+
execution and changed state). Despite being asynchronous, however, the order of execution of Promise objects
83+
in a chain (or tree) is strictly defined.
84+
85+
There are multiple ways to create and execute a new `Promise`. Both ways provide identical behavior:
86+
87+
```ruby
88+
# create, operate, then execute
89+
p1 = Concurrent::Promise.new{ "Hello World!" }
90+
p1.state #=> :unscheduled
91+
p1.execute
92+
93+
# create and immediately execute
94+
p2 = Concurrent::Promise.new{ "Hello World!" }.execute
95+
96+
# execute during creation
97+
p3 = Concurrent::Promise.execute{ "Hello World!" }
98+
```
99+
100+
Once the `execute` method is called a `Promise` becomes `pending`:
81101

82102
```ruby
83103
p = Concurrent::Promise.execute{ "Hello, world!" }
84-
p.state #=> :pending
104+
p.state #=> :pending
85105
p.pending? #=> true
86106
```
87107

88-
Wait a little bit, and the promise will resolve and provide a value
108+
Wait a little bit, and the promise will resolve and provide a value:
89109

90110
```ruby
91111
p = Concurrent::Promise.execute{ "Hello, world!" }
@@ -97,7 +117,7 @@ p.value #=> "Hello, world!"
97117
```
98118

99119
If an exception occurs, the promise will be rejected and will provide
100-
a reason for the rejection
120+
a reason for the rejection:
101121

102122
```ruby
103123
p = Concurrent::Promise.execute{ raise StandardError.new("Here comes the Boom!") }
@@ -110,30 +130,30 @@ p.reason #=> "#<StandardError: Here comes the Boom!>"
110130

111131
### Rejection
112132

113-
When a promise is rejected all its children will be receive the reason as the rejection callable parameter.
133+
When a promise is rejected all its children will be rejected and will receive the rejection `reason` as the
134+
rejection callable parameter:
114135

115136
```ruby
116137
p = [ Concurrent::Promise.execute{ Thread.pass; raise StandardError } ]
117138

118139
c1 = p.then(Proc.new{ |reason| 42 })
119140
c2 = p.then(Proc.new{ |reason| raise 'Boom!' })
120141

121-
122142
sleep(0.1)
123143

124-
c1.state #=> :fulfilled
144+
c1.state #=> :rejected
125145
c2.state #=> :rejected
126146
```
127147

128148
Once a promise is rejected it will continue to accept children that will receive immediately
129-
rejection (they will be executed asynchronously)
149+
rejection (they will be executed asynchronously).
130150

131151
### Aliases
132152

133-
`then` method is the most generic one: it accepts a block to be executed upon parent fulfillment
153+
The `then` method is the most generic alias: it accepts a block to be executed upon parent fulfillment
134154
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.
155+
The default block is `{ |result| result }` that fulfills the child with the parent value.
156+
The default callable is `{ |reason| raise reason }` that rejects the child with the parent reason.
137157

138158
`on_success { |result| ... }` is the same as `then {|result| ... }`
139159
`rescue { |reason| ... }` is the same as `then(Proc.new { |reason| ... } )`

0 commit comments

Comments
 (0)