-
Notifications
You must be signed in to change notification settings - Fork 26
/
Copy pathpromise.rb
224 lines (182 loc) · 4.6 KB
/
promise.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
# encoding: utf-8
require 'promise/version'
require 'promise/observer'
require 'promise/progress'
require 'promise/group'
class Promise
Error = Class.new(RuntimeError)
BrokenError = Class.new(Error)
include Promise::Progress
include Promise::Observer
attr_accessor :source
attr_reader :state, :value, :reason
def self.resolve(obj = nil)
return obj if obj.is_a?(self)
new.tap { |promise| promise.fulfill(obj) }
end
def self.all(enumerable)
Group.new(new, enumerable).promise
end
if Gem.ruby_version >= Gem::Version.new('2.6.0')
# @deprecated Use {Object#then} instead
def self.map_value(obj)
obj.then { |value| yield value }
end
else
def self.map_value(obj)
if obj.is_a?(Promise)
obj.then { |value| yield value }
else
yield obj
end
end
end
def self.sync(obj)
obj.is_a?(Promise) ? obj.sync : obj
end
def initialize
@state = :pending
end
def pending?
state.equal?(:pending)
end
def fulfilled?
state.equal?(:fulfilled)
end
def rejected?
state.equal?(:rejected)
end
def then(on_fulfill = nil, on_reject = nil, &block)
on_fulfill ||= block
next_promise = self.class.new
case state
when :fulfilled
defer { next_promise.promise_fulfilled(value, on_fulfill) }
when :rejected
defer { next_promise.promise_rejected(reason, on_reject) }
else
next_promise.source = self
subscribe(next_promise, on_fulfill, on_reject)
end
next_promise
end
def rescue(&block)
self.then(nil, block)
end
alias_method :catch, :rescue
def sync
if pending?
wait
raise BrokenError if pending?
end
raise reason if rejected?
value
end
def fulfill(value = nil)
return self unless pending?
if value.is_a?(Promise)
case value.state
when :fulfilled
fulfill(value.value)
when :rejected
reject(value.reason)
else
@source = value
value.subscribe(self, nil, nil)
end
else
@source = nil
@state = :fulfilled
@value = value
notify_fulfillment if defined?(@observers)
end
self
end
def reject(reason = nil)
return self unless pending?
@source = nil
@state = :rejected
@reason = reason_coercion(reason || Error)
notify_rejection if defined?(@observers)
self
end
# Override to support sync on a promise without a source or to wait
# for deferred callbacks on the source
def wait
while source
saved_source = source
saved_source.wait
break if saved_source.equal?(source)
end
end
# Subscribe the given `observer` for status changes of a `Promise`.
#
# The observer will be notified about state changes of the promise
# by calls to its `#promise_fulfilled` or `#promise_rejected` methods.
#
# These methods will be called with two arguments,
# the first being the observed `Promise`, the second being the
# `on_fulfill_arg` or `on_reject_arg` given to `#subscribe`.
#
# @param [Promise::Observer] observer
# @param [Object] on_fulfill_arg
# @param [Object] on_reject_arg
def subscribe(observer, on_fulfill_arg, on_reject_arg)
raise Error, 'Non-pending promises can not be observed' unless pending?
unless observer.is_a?(Observer)
raise ArgumentError, 'Expected `observer` to be a `Promise::Observer`'
end
@observers ||= []
@observers.push(observer, on_fulfill_arg, on_reject_arg)
end
protected
# Override to defer calling the callback for Promises/A+ spec compliance
def defer
yield
end
def promise_fulfilled(value, on_fulfill)
if on_fulfill
settle_from_handler(value, &on_fulfill)
else
fulfill(value)
end
end
def promise_rejected(reason, on_reject)
if on_reject
settle_from_handler(reason, &on_reject)
else
reject(reason)
end
end
private
def reason_coercion(reason)
case reason
when Exception
reason.set_backtrace(caller) unless reason.backtrace
when Class
reason = reason_coercion(reason.new) if reason <= Exception
end
reason
end
def notify_fulfillment
defer do
@observers.each_slice(3) do |observer, on_fulfill_arg|
observer.promise_fulfilled(value, on_fulfill_arg)
end
@observers = nil
end
end
def notify_rejection
defer do
@observers.each_slice(3) do |observer, _on_fulfill_arg, on_reject_arg|
observer.promise_rejected(reason, on_reject_arg)
end
@observers = nil
end
end
def settle_from_handler(value)
fulfill(yield(value))
rescue => ex
reject(ex)
end
end