Skip to content

Commit 90063b5

Browse files
committed
Add Promise library
Also incorporate fix: syrusakbary/promise#89
1 parent 741bca2 commit 90063b5

File tree

10 files changed

+2212
-0
lines changed

10 files changed

+2212
-0
lines changed

strawberry/promise/__init__.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from .promise import Promise, is_thenable
2+
3+
4+
__all__ = [
5+
"Promise",
6+
"is_thenable",
7+
]

strawberry/promise/async_.py

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
# Copy of the Promise library (https://github.com/syrusakbary/promise) with some
2+
# modifications.
3+
#
4+
# Promise is licensed under the terms of the MIT license, reproduced below.
5+
#
6+
# = = = = =
7+
#
8+
# The MIT License (MIT)
9+
#
10+
# Copyright (c) 2016 Syrus Akbary
11+
#
12+
# Permission is hereby granted, free of charge, to any person obtaining a copy
13+
# of this software and associated documentation files (the "Software"), to deal
14+
# in the Software without restriction, including without limitation the rights
15+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16+
# copies of the Software, and to permit persons to whom the Software is
17+
# furnished to do so, subject to the following conditions:
18+
#
19+
# The above copyright notice and this permission notice shall be included in all
20+
# copies or substantial portions of the Software.
21+
#
22+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28+
# SOFTWARE.
29+
30+
# Based on https://github.com/petkaantonov/bluebird/blob/master/src/promise.js
31+
from collections import deque
32+
from threading import local
33+
from typing import Any, Callable, Optional
34+
35+
36+
if False:
37+
from .promise import Promise
38+
39+
40+
class Async(local):
41+
def __init__(self, trampoline_enabled=True):
42+
self.is_tick_used = False
43+
self.late_queue = deque() # type: ignore
44+
self.normal_queue = deque() # type: ignore
45+
self.have_drained_queues = False
46+
self.trampoline_enabled = trampoline_enabled
47+
48+
def enable_trampoline(self):
49+
self.trampoline_enabled = True
50+
51+
def disable_trampoline(self):
52+
self.trampoline_enabled = False
53+
54+
def have_items_queued(self):
55+
return self.is_tick_used or self.have_drained_queues
56+
57+
def _async_invoke_later(self, fn, scheduler):
58+
self.late_queue.append(fn)
59+
self.queue_tick(scheduler)
60+
61+
def _async_invoke(self, fn, scheduler):
62+
# type: (Callable, Any) -> None
63+
self.normal_queue.append(fn)
64+
self.queue_tick(scheduler)
65+
66+
def _async_settle_promise(self, promise):
67+
# type: (Promise) -> None
68+
self.normal_queue.append(promise)
69+
self.queue_tick(promise.scheduler)
70+
71+
def invoke(self, fn, scheduler):
72+
# type: (Callable, Any) -> None
73+
if self.trampoline_enabled:
74+
self._async_invoke(fn, scheduler)
75+
else:
76+
scheduler.call(fn)
77+
78+
def settle_promises(self, promise):
79+
# type: (Promise) -> None
80+
if self.trampoline_enabled:
81+
self._async_settle_promise(promise)
82+
else:
83+
promise.scheduler.call(promise._settle_promises)
84+
85+
def throw_later(self, reason, scheduler):
86+
# type: (Exception, Any) -> None
87+
def fn():
88+
# type: () -> None
89+
raise reason
90+
91+
scheduler.call(fn)
92+
93+
fatal_error = throw_later
94+
95+
def drain_queue(self, queue):
96+
# type: (deque) -> None
97+
from .promise import Promise
98+
99+
while queue:
100+
fn = queue.popleft()
101+
if isinstance(fn, Promise):
102+
fn._settle_promises()
103+
continue
104+
fn()
105+
106+
def drain_queue_until_resolved(self, promise):
107+
# type: (Promise) -> None
108+
from .promise import Promise
109+
110+
queue = self.normal_queue
111+
while queue:
112+
if not promise.is_pending:
113+
return
114+
fn = queue.popleft()
115+
if isinstance(fn, Promise):
116+
fn._settle_promises()
117+
continue
118+
fn()
119+
120+
self.reset()
121+
self.have_drained_queues = True
122+
self.drain_queue(self.late_queue)
123+
124+
def wait(self, promise, timeout=None):
125+
# type: (Promise, Optional[float]) -> None
126+
if not promise.is_pending:
127+
# We return if the promise is already
128+
# fulfilled or rejected
129+
return
130+
131+
target = promise._target()
132+
133+
if self.trampoline_enabled:
134+
if self.is_tick_used:
135+
self.drain_queue_until_resolved(target)
136+
137+
if not promise.is_pending:
138+
# We return if the promise is already
139+
# fulfilled or rejected
140+
return
141+
target.scheduler.wait(target, timeout)
142+
143+
def drain_queues(self):
144+
# type: () -> None
145+
assert self.is_tick_used
146+
self.drain_queue(self.normal_queue)
147+
self.reset()
148+
self.have_drained_queues = True
149+
self.drain_queue(self.late_queue)
150+
151+
def queue_tick(self, scheduler):
152+
# type: (Any) -> None
153+
if not self.is_tick_used:
154+
self.is_tick_used = True
155+
scheduler.call(self.drain_queues)
156+
157+
def reset(self):
158+
# type: () -> None
159+
self.is_tick_used = False

0 commit comments

Comments
 (0)