core-async 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2f2c40406be47b9385d6252ddfc534383b817249c44aeda7e3410ea12d15afde
4
- data.tar.gz: 48ee77b1bec473726d7deb3f35481c487e946c8fd00668c039d7556164bd70fc
3
+ metadata.gz: 36564cdb777065399358b390e554b89b367902f9d065ed3def243a0816b17049
4
+ data.tar.gz: ec4cb82ad1b7a97d60af94fc4be07aca732f03f9fe5db91367d7628631d79bc7
5
5
  SHA512:
6
- metadata.gz: 21f8e4bf33f14497c2080450f6ba4db0abde814ffbb86a3b0ed64f187edfd4c042830fcccd4c9044b5ff67d31bdf25904b2430bf715c02ea0334c7152cbe597d
7
- data.tar.gz: b41c4e064774fc736814255aa5f92dfd160967c0370c7a5b76784418793a2550e0403beb6524e37b488ea76e9db25c7f1810b8919f699b9182a3268feaa44431
6
+ metadata.gz: 7cf03e8b149e9e0d5185d907025fd833c6d63074be1461970862ae138f3f3744e361422068d8bd4e8696f28fe459d65d7f3e2caa153c2b0f5b374f8d550e21da
7
+ data.tar.gz: 11b9a8ea0a25975bbf9f92ca7b41c1f0db6b2a7ae2ab0a16bbf6d3057d6d6add5875b30b3646e4028747f4eebb8863769fe8403a825c6e5a8a71cd25e677de22
data/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## [v0.2.0](https://github.com/metabahn/corerb/releases/tag/2021-03-05)
2
+
3
+ *released on 2021-03-05*
4
+
5
+ * `add` [#17](https://github.com/metabahn/corerb/pull/17) Add Core::Async::Collection for building async collections ([bryanp](https://github.com/bryanp))
6
+ * `add` [#16](https://github.com/metabahn/corerb/pull/16) Add Core::Async::Enumerator for async enumeration ([bryanp](https://github.com/bryanp))
7
+ * `add` [#15](https://github.com/metabahn/corerb/pull/15) Add Is::Async#resolve for resolving potential futures to final values ([bryanp](https://github.com/bryanp))
8
+ * `add` [#14](https://github.com/metabahn/corerb/pull/14) Add Is::Async#aware for guaranteeing an async context without nesting ([bryanp](https://github.com/bryanp))
9
+ * `fix` [#13](https://github.com/metabahn/corerb/pull/13) Correctly return the result from top-level await ([bryanp](https://github.com/bryanp))
10
+ * `fix` [#12](https://github.com/metabahn/corerb/pull/12) Resolve some edge-cases and bugs around raising top-level errors ([bryanp](https://github.com/bryanp))
11
+ * `chg` [#11](https://github.com/metabahn/corerb/pull/11) Raise an argument error when attempting to wrap a non-async-aware object ([bryanp](https://github.com/bryanp))
12
+ * `add` [#10](https://github.com/metabahn/corerb/pull/10) Add a pattern for making a call explicitly async ([bryanp](https://github.com/bryanp))
13
+ * `add` [#9](https://github.com/metabahn/corerb/pull/9) Introduce async futures ([bryanp](https://github.com/bryanp))
14
+
1
15
  ## [v0.1.0](https://github.com/metabahn/corerb/releases/tag/2021-02-11)
2
16
 
3
17
  *released on 2021-02-11*
data/lib/core/async.rb CHANGED
@@ -1,7 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "async/bootstrap"
4
+
3
5
  module Core
4
6
  module Async
7
+ require_relative "async/collection"
8
+ require_relative "async/enumerator"
9
+ require_relative "async/future"
5
10
  require_relative "async/reactor"
6
11
  require_relative "async/version"
7
12
  end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "async"
4
+ Console.logger.off!
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../is/async"
4
+
5
+ module Core
6
+ module Async
7
+ class Collection
8
+ class << self
9
+ include Is::Async
10
+
11
+ # [public] Builds a collection from an enumerable object.
12
+ #
13
+ def build(object)
14
+ aware do
15
+ values = object.map { |value|
16
+ async do
17
+ if block_given?
18
+ yield value
19
+ else
20
+ value
21
+ end
22
+ end
23
+ }
24
+
25
+ new(values)
26
+ end
27
+ end
28
+ end
29
+
30
+ include Enumerable
31
+ include Is::Async
32
+
33
+ def initialize(values = [])
34
+ unless values.respond_to?(:each)
35
+ raise ArgumentError, "values are not enumerable"
36
+ end
37
+
38
+ @values = values
39
+ end
40
+
41
+ # [public] Adds a value to the collection.
42
+ #
43
+ def <<(value)
44
+ @values << value
45
+ end
46
+ alias_method :add, :<<
47
+
48
+ # [public] Yields each resolved value.
49
+ #
50
+ def each
51
+ return to_enum(:each) unless block_given?
52
+
53
+ @values.each do |value|
54
+ yield resolve(value)
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../is/async"
4
+
5
+ module Core
6
+ module Async
7
+ class Enumerator
8
+ include Enumerable
9
+ include Is::Async
10
+
11
+ def initialize(object)
12
+ unless object.respond_to?(:each)
13
+ raise ArgumentError, "object is not enumerable"
14
+ end
15
+
16
+ @object = object
17
+ end
18
+
19
+ # [public] Yields each value within its own async context, waiting on the enumeration to complete.
20
+ #
21
+ def each
22
+ unless block_given?
23
+ return to_enum(:each)
24
+ end
25
+
26
+ await do
27
+ errored = false
28
+ @object.each do |value|
29
+ break if errored
30
+
31
+ async {
32
+ begin
33
+ yield value
34
+ rescue => error
35
+ errored = true
36
+ raise error
37
+ ensure
38
+ defer
39
+ end
40
+ }
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Core
4
+ module Async
5
+ # [public] Represents a future result.
6
+ #
7
+ class Future
8
+ def initialize(task)
9
+ @task = task
10
+ @error = nil
11
+ end
12
+
13
+ # [public] Wait on the task to complete, returning self.
14
+ #
15
+ def wait
16
+ unless @error
17
+ resolve
18
+ end
19
+
20
+ self
21
+ end
22
+
23
+ # [public] Return the result, blocking until available.
24
+ #
25
+ def result
26
+ @error || @result ||= resolve
27
+ end
28
+
29
+ private def resolve
30
+ wait_all(@task)
31
+
32
+ resolve_value(@task.result)
33
+ rescue => error
34
+ @error = error
35
+
36
+ raise error
37
+ end
38
+
39
+ # [public] Return any error that occurred without re-raising, blocking until available.
40
+ #
41
+ def error
42
+ @error ||= get_error
43
+ end
44
+
45
+ private def get_error
46
+ result
47
+ nil
48
+ rescue => error
49
+ error
50
+ end
51
+
52
+ # [public] Return the status; one of :pending, :failed, or :complete.
53
+ #
54
+ def status
55
+ if defined?(@status)
56
+ @status
57
+ else
58
+ status = find_status
59
+ if terminal_status?(status)
60
+ @status = status
61
+ end
62
+
63
+ status
64
+ end
65
+ end
66
+
67
+ private def find_status
68
+ case @task.status
69
+ when :running
70
+ :pending
71
+ when :failed
72
+ :failed
73
+ when :complete
74
+ :complete
75
+ end
76
+ end
77
+
78
+ private def terminal_status?(status)
79
+ status == :failed || status == :complete
80
+ end
81
+
82
+ # [public] Return `true` if pending.
83
+ #
84
+ def pending?
85
+ status == :pending
86
+ end
87
+
88
+ # [public] Return `true` if failed.
89
+ #
90
+ def failed?
91
+ status == :failed
92
+ end
93
+
94
+ # [public] Return `true` if complete.
95
+ #
96
+ def complete?
97
+ status == :complete
98
+ end
99
+
100
+ # [public] Return `true` if failed or complete.
101
+ #
102
+ def resolved?
103
+ failed? || complete?
104
+ end
105
+
106
+ private def resolve_value(value)
107
+ if value.is_a?(self.class)
108
+ resolve_value(value.result)
109
+ else
110
+ value
111
+ end
112
+ end
113
+
114
+ private def wait_all(task)
115
+ task.children&.each do |child|
116
+ wait_all(child)
117
+ end
118
+
119
+ task.wait
120
+ end
121
+ end
122
+ end
123
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Core
4
4
  module Async
5
- VERSION = "0.1.0"
5
+ VERSION = "0.2.0"
6
6
 
7
7
  def self.version
8
8
  VERSION
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Core
4
+ module Async
5
+ # [public] Wraps an object, calling all methods in an async context.
6
+ #
7
+ class Wrapper < BasicObject
8
+ def initialize(target)
9
+ unless target.respond_to?(:async)
10
+ ::Kernel.raise ::ArgumentError, "object is not async-aware"
11
+ end
12
+
13
+ @target = target
14
+ end
15
+
16
+ def method_missing(name, *args, &block)
17
+ @target.async do
18
+ @target.public_send(name, *args, &block)
19
+ end
20
+ end
21
+
22
+ ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords)
23
+
24
+ def respond_to_missing?(name, *)
25
+ true
26
+ end
27
+ end
28
+ end
29
+ end
data/lib/is/async.rb CHANGED
@@ -1,38 +1,66 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "async"
4
- Console.logger.off!
5
-
3
+ require_relative "../core/async/bootstrap"
4
+ require_relative "../core/async/future"
6
5
  require_relative "../core/async/timeout"
6
+ require_relative "../core/async/wrapper"
7
7
 
8
8
  module Is
9
9
  # [public] Makes Ruby objects async-aware.
10
10
  #
11
11
  module Async
12
- # [public] Call asynchronous behavior in a proper async context.
12
+ # [public] Call behavior asychronously, returning a future.
13
13
  #
14
14
  def async
15
- ::Async::Reactor.run do
16
- yield
15
+ if block_given?
16
+ task = ::Async::Reactor.run { |current|
17
+ begin
18
+ yield
19
+ ensure
20
+ current.yield
21
+ end
22
+ }
23
+
24
+ Core::Async::Future.new(task)
25
+ else
26
+ Core::Async::Wrapper.new(self)
17
27
  end
18
28
  end
19
29
 
20
- # [public] Call asynchronous behavior synchronously in a proper async context.
30
+ # [public] Call behavior synchronously but within an async context, waiting on the result.
21
31
  #
22
32
  def await
23
33
  if (task = ::Async::Task.current?)
24
- reference = task.async {
25
- yield
34
+ reference = task.async { |current|
35
+ begin
36
+ yield
37
+ ensure
38
+ current.yield
39
+ end
26
40
  }
27
41
 
28
42
  wait_all(reference)
29
43
  else
30
- ::Async::Reactor.run {
31
- yield
44
+ ::Async::Reactor.run { |task|
45
+ async {
46
+ yield
47
+ }.result
32
48
  }.wait
33
49
  end
34
50
  end
35
51
 
52
+ # [public] Call behavior within an async context without additional nesting.
53
+ #
54
+ def aware
55
+ if ::Async::Task.current?
56
+ yield
57
+ else
58
+ await do
59
+ yield
60
+ end
61
+ end
62
+ end
63
+
36
64
  # [public] Sleeps for `seconds` in a proper async context.
37
65
  #
38
66
  def sleep(seconds)
@@ -65,6 +93,17 @@ module Is
65
93
  end
66
94
  end
67
95
 
96
+ # [public] Resolves a potential future to a final result.
97
+ #
98
+ def resolve(value)
99
+ case value
100
+ when Core::Async::Future
101
+ value.result
102
+ else
103
+ value
104
+ end
105
+ end
106
+
68
107
  private def internal_async
69
108
  ::Async::Reactor.run do |task|
70
109
  yield task
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: core-async
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bryan Powell
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-02-12 00:00:00.000000000 Z
11
+ date: 2021-03-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: async
@@ -33,9 +33,14 @@ files:
33
33
  - CHANGELOG.md
34
34
  - LICENSE
35
35
  - lib/core/async.rb
36
+ - lib/core/async/bootstrap.rb
37
+ - lib/core/async/collection.rb
38
+ - lib/core/async/enumerator.rb
39
+ - lib/core/async/future.rb
36
40
  - lib/core/async/reactor.rb
37
41
  - lib/core/async/timeout.rb
38
42
  - lib/core/async/version.rb
43
+ - lib/core/async/wrapper.rb
39
44
  - lib/is/async.rb
40
45
  homepage: https://github.com/metabahn/corerb/
41
46
  licenses: