|
5 | 5 |
|
6 | 6 | module Concurrent
|
7 | 7 |
|
8 |
| - # {include:file:doc/promise.md} |
| 8 | + PromiseExecutionError = Class.new(StandardError) |
| 9 | + |
| 10 | + # Promises are inspired by the JavaScript [Promises/A](http://wiki.commonjs.org/wiki/Promises/A) |
| 11 | + # and [Promises/A+](http://promises-aplus.github.io/promises-spec/) specifications. |
| 12 | + # |
| 13 | + # > A promise represents the eventual value returned from the single completion of an operation. |
| 14 | + # |
| 15 | + # Promises are similar to futures and share many of the same behaviours. Promises are far more |
| 16 | + # robust, however. Promises can be chained in a tree structure where each promise may have zero |
| 17 | + # or more children. Promises are chained using the `then` method. The result of a call to `then` |
| 18 | + # is always another promise. Promises are resolved asynchronously (with respect to the main thread) |
| 19 | + # but in a strict order: parents are guaranteed to be resolved before their children, children |
| 20 | + # before their younger siblings. The `then` method takes two parameters: an optional block to |
| 21 | + # be executed upon parent resolution and an optional callable to be executed upon parent failure. |
| 22 | + # The result of each promise is passed to each of its children upon resolution. When a promise |
| 23 | + # is rejected all its children will be summarily rejected and will receive the reason. |
| 24 | + # |
| 25 | + # Promises have four possible states: *unscheduled*, *pending*, *rejected*, and *fulfilled*. A |
| 26 | + # Promise created using `.new` will be *unscheduled*. It is scheduled by calling the `execute` |
| 27 | + # method. Upon execution the Promise and all its children will be set to *pending*. When a promise |
| 28 | + # is *pending* it will remain in that state until processing is complete. A completed Promise is |
| 29 | + # either *rejected*, indicating that an exception was thrown during processing, or *fulfilled*, |
| 30 | + # indicating it succeeded. If a Promise is *fulfilled* its `value` will be updated to reflect |
| 31 | + # the result of the operation. If *rejected* the `reason` will be updated with a reference to |
| 32 | + # the thrown exception. The predicate methods `unscheduled?`, `pending?`, `rejected?`, and |
| 33 | + # `fulfilled?` can be called at any time to obtain the state of the Promise, as can the `state` |
| 34 | + # method, which returns a symbol. A Promise created using `.execute` will be *pending*, a Promise |
| 35 | + # created using `.fulfill(value)` will be *fulfilled* with the given value and a Promise created |
| 36 | + # using `.reject(reason)` will be *rejected* with the given reason. |
| 37 | + # |
| 38 | + # Retrieving the value of a promise is done through the `value` (alias: `deref`) method. Obtaining |
| 39 | + # the value of a promise is a potentially blocking operation. When a promise is *rejected* a call |
| 40 | + # to `value` will return `nil` immediately. When a promise is *fulfilled* a call to `value` will |
| 41 | + # immediately return the current value. When a promise is *pending* a call to `value` will block |
| 42 | + # until the promise is either *rejected* or *fulfilled*. A *timeout* value can be passed to `value` |
| 43 | + # to limit how long the call will block. If `nil` the call will block indefinitely. If `0` the call |
| 44 | + # will not block. Any other integer or float value will indicate the maximum number of seconds to block. |
| 45 | + # |
| 46 | + # Promises run on the global thread pool. |
| 47 | + # |
| 48 | + # ### Examples |
| 49 | + # |
| 50 | + # Start by requiring promises |
| 51 | + # |
| 52 | + # ```ruby |
| 53 | + # require 'concurrent' |
| 54 | + # ``` |
| 55 | + # |
| 56 | + # Then create one |
| 57 | + # |
| 58 | + # ```ruby |
| 59 | + # p = Concurrent::Promise.execute do |
| 60 | + # # do something |
| 61 | + # 42 |
| 62 | + # end |
| 63 | + # ``` |
| 64 | + # |
| 65 | + # Promises can be chained using the `then` method. The `then` method accepts a block, to be executed |
| 66 | + # on fulfillment, and a callable argument to be executed on rejection. The result of the each promise |
| 67 | + # is passed as the block argument to chained promises. |
| 68 | + # |
| 69 | + # ```ruby |
| 70 | + # p = Concurrent::Promise.new{10}.then{|x| x * 2}.then{|result| result - 10 }.execute |
| 71 | + # ``` |
| 72 | + # |
| 73 | + # And so on, and so on, and so on... |
| 74 | + # |
| 75 | + # ```ruby |
| 76 | + # p = Concurrent::Promise.fulfill(20). |
| 77 | + # then{|result| result - 10 }. |
| 78 | + # then{|result| result * 3 }. |
| 79 | + # then{|result| result % 5 }.execute |
| 80 | + # ``` |
| 81 | + # |
| 82 | + # The initial state of a newly created Promise depends on the state of its parent: |
| 83 | + # - if parent is *unscheduled* the child will be *unscheduled* |
| 84 | + # - if parent is *pending* the child will be *pending* |
| 85 | + # - if parent is *fulfilled* the child will be *pending* |
| 86 | + # - if parent is *rejected* the child will be *pending* (but will ultimately be *rejected*) |
| 87 | + # |
| 88 | + # Promises are executed asynchronously from the main thread. By the time a child Promise finishes |
| 89 | + # nitialization it may be in a different state that its parent (by the time a child is created its parent |
| 90 | + # may have completed execution and changed state). Despite being asynchronous, however, the order of |
| 91 | + # execution of Promise objects in a chain (or tree) is strictly defined. |
| 92 | + # |
| 93 | + # There are multiple ways to create and execute a new `Promise`. Both ways provide identical behavior: |
| 94 | + # |
| 95 | + # ```ruby |
| 96 | + # # create, operate, then execute |
| 97 | + # p1 = Concurrent::Promise.new{ "Hello World!" } |
| 98 | + # p1.state #=> :unscheduled |
| 99 | + # p1.execute |
| 100 | + # |
| 101 | + # # create and immediately execute |
| 102 | + # p2 = Concurrent::Promise.new{ "Hello World!" }.execute |
| 103 | + # |
| 104 | + # # execute during creation |
| 105 | + # p3 = Concurrent::Promise.execute{ "Hello World!" } |
| 106 | + # ``` |
| 107 | + # |
| 108 | + # Once the `execute` method is called a `Promise` becomes `pending`: |
| 109 | + # |
| 110 | + # ```ruby |
| 111 | + # p = Concurrent::Promise.execute{ "Hello, world!" } |
| 112 | + # p.state #=> :pending |
| 113 | + # p.pending? #=> true |
| 114 | + # ``` |
| 115 | + # |
| 116 | + # Wait a little bit, and the promise will resolve and provide a value: |
| 117 | + # |
| 118 | + # ```ruby |
| 119 | + # p = Concurrent::Promise.execute{ "Hello, world!" } |
| 120 | + # sleep(0.1) |
| 121 | + # |
| 122 | + # p.state #=> :fulfilled |
| 123 | + # p.fulfilled? #=> true |
| 124 | + # p.value #=> "Hello, world!" |
| 125 | + # ``` |
| 126 | + # |
| 127 | + # If an exception occurs, the promise will be rejected and will provide |
| 128 | + # a reason for the rejection: |
| 129 | + # |
| 130 | + # ```ruby |
| 131 | + # p = Concurrent::Promise.execute{ raise StandardError.new("Here comes the Boom!") } |
| 132 | + # sleep(0.1) |
| 133 | + # |
| 134 | + # p.state #=> :rejected |
| 135 | + # p.rejected? #=> true |
| 136 | + # p.reason #=> "#<StandardError: Here comes the Boom!>" |
| 137 | + # ``` |
| 138 | + # |
| 139 | + # #### Rejection |
| 140 | + # |
| 141 | + # When a promise is rejected all its children will be rejected and will receive the rejection `reason` |
| 142 | + # as the rejection callable parameter: |
| 143 | + # |
| 144 | + # ```ruby |
| 145 | + # p = [ Concurrent::Promise.execute{ Thread.pass; raise StandardError } ] |
| 146 | + # |
| 147 | + # c1 = p.then(Proc.new{ |reason| 42 }) |
| 148 | + # c2 = p.then(Proc.new{ |reason| raise 'Boom!' }) |
| 149 | + # |
| 150 | + # sleep(0.1) |
| 151 | + # |
| 152 | + # c1.state #=> :rejected |
| 153 | + # c2.state #=> :rejected |
| 154 | + # ``` |
| 155 | + # |
| 156 | + # Once a promise is rejected it will continue to accept children that will receive immediately rejection |
| 157 | + # (they will be executed asynchronously). |
| 158 | + # |
| 159 | + # #### Aliases |
| 160 | + # |
| 161 | + # The `then` method is the most generic alias: it accepts a block to be executed upon parent fulfillment |
| 162 | + # and a callable to be executed upon parent rejection. At least one of them should be passed. The default |
| 163 | + # block is `{ |result| result }` that fulfills the child with the parent value. The default callable is |
| 164 | + # `{ |reason| raise reason }` that rejects the child with the parent reason. |
| 165 | + # |
| 166 | + # - `on_success { |result| ... }` is the same as `then {|result| ... }` |
| 167 | + # - `rescue { |reason| ... }` is the same as `then(Proc.new { |reason| ... } )` |
| 168 | + # - `rescue` is aliased by `catch` and `on_error` |
9 | 169 | class Promise
|
10 | 170 | # TODO unify promise and future to single class, with dataflow
|
11 | 171 | include Obligation
|
@@ -168,8 +328,66 @@ def zip(*others)
|
168 | 328 | self.class.zip(self, *others)
|
169 | 329 | end
|
170 | 330 |
|
| 331 | + # Aggregates a collection of promises and executes the `then` condition |
| 332 | + # if all aggregated promises succeed. Executes the `rescue` handler with |
| 333 | + # a `Concurrent::PromiseExecutionError` if any of the aggregated promises |
| 334 | + # fail. Upon execution will execute any of the aggregate promises that |
| 335 | + # were not already executed. |
| 336 | + # |
| 337 | + # @!macro [attach] promise_self_aggregate |
| 338 | + # |
| 339 | + # The returned promise will not yet have been executed. Additional `#then` |
| 340 | + # and `#rescue` handlers may still be provided. Once the returned promise |
| 341 | + # is execute the aggregate promises will be also be executed (if they have |
| 342 | + # not been executed already). The results of the aggregate promises will |
| 343 | + # be checked upon completion. The necessary `#then` and `#rescue` blocks |
| 344 | + # on the aggregating promise will then be executed as appropriate. If the |
| 345 | + # `#rescue` handlers are executed the raises exception will be |
| 346 | + # `Concurrent::PromiseExecutionError`. |
| 347 | + # |
| 348 | + # @param [Array] promises Zero or more promises to aggregate |
| 349 | + # @return [Promise] an unscheduled (not executed) promise that aggregates |
| 350 | + # the promises given as arguments |
| 351 | + def self.all?(*promises) |
| 352 | + aggregate(:all?, *promises) |
| 353 | + end |
| 354 | + |
| 355 | + # Aggregates a collection of promises and executes the `then` condition |
| 356 | + # if any aggregated promises succeed. Executes the `rescue` handler with |
| 357 | + # a `Concurrent::PromiseExecutionError` if any of the aggregated promises |
| 358 | + # fail. Upon execution will execute any of the aggregate promises that |
| 359 | + # were not already executed. |
| 360 | + # |
| 361 | + # @!macro promise_self_aggregate |
| 362 | + def self.any?(*promises) |
| 363 | + aggregate(:any?, *promises) |
| 364 | + end |
| 365 | + |
171 | 366 | protected
|
172 | 367 |
|
| 368 | + # Aggregate a collection of zero or more promises under a composite promise, |
| 369 | + # execute the aggregated promises and collect them into a standard Ruby array, |
| 370 | + # call the given Ruby `Ennnumerable` predicate (such as `any?`, `all?`, `none?`, |
| 371 | + # or `one?`) on the collection checking for the success or failure of each, |
| 372 | + # then executing the composite's `#then` handlers if the predicate returns |
| 373 | + # `true` or executing the composite's `#rescue` handlers if the predicate |
| 374 | + # returns false. |
| 375 | + # |
| 376 | + # @!macro promise_self_aggregate |
| 377 | + def self.aggregate(method, *promises) |
| 378 | + composite = Promise.new do |
| 379 | + completed = promises.collect do |promise| |
| 380 | + promise.execute if promise.unscheduled? |
| 381 | + promise.wait |
| 382 | + promise |
| 383 | + end |
| 384 | + unless completed.empty? || completed.send(method){|promise| promise.fulfilled? } |
| 385 | + raise PromiseExecutionError |
| 386 | + end |
| 387 | + end |
| 388 | + composite |
| 389 | + end |
| 390 | + |
173 | 391 | def set_pending
|
174 | 392 | mutex.synchronize do
|
175 | 393 | @state = :pending
|
|
0 commit comments