@pipedog/asyncio 0.0.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.
@@ -0,0 +1,746 @@
1
+ // Copyright (c) 2026, pipedog contributors
2
+ // SPDX-License-Identifier: BSD-2-Clause
3
+ //
4
+ // asyncio synchronization primitives and helpers, ported from CPython's
5
+ // asyncio (locks.py, queues.py, tasks.py, timeouts.py) on top of Future, so
6
+ // they wake waiters exactly as asyncio does: through the ready queue, never
7
+ // synchronously. Every wait here is a cancellation point (see task.ts).
8
+ // Durations are in seconds, as in Python.
9
+ import { callLater, callSoon } from "./loop.js";
10
+ import { abortable, awaitFuture, CancelledError, createTask, currentTask, Future, TimeoutError, } from "./task.js";
11
+ /** Monotonic time in seconds (loop.time()). */
12
+ export function monotonic() {
13
+ return performance.now() / 1000;
14
+ }
15
+ /** Monotonic time in nanoseconds (time.monotonic_ns()). */
16
+ export function monotonicNs() {
17
+ return Math.round(performance.now() * 1_000_000);
18
+ }
19
+ /**
20
+ * Wall-clock time in seconds (time.time()).
21
+ *
22
+ * Sub-millisecond, like Python's: `Date.now()` alone would give successive
23
+ * readings the same value, so intervals between frames pushed back to back
24
+ * would measure as zero. Falls back to `Date.now()` where the runtime has no
25
+ * time origin.
26
+ */
27
+ export function time() {
28
+ const origin = performance.timeOrigin;
29
+ return origin ? (origin + performance.now()) / 1000 : Date.now() / 1000;
30
+ }
31
+ function remove(items, item) {
32
+ const i = items.indexOf(item);
33
+ if (i < 0)
34
+ return false;
35
+ items.splice(i, 1);
36
+ return true;
37
+ }
38
+ /**
39
+ * asyncio.sleep(seconds). `sleep(0)` yields once: the task goes to the back
40
+ * of the ready queue, so everything already scheduled runs first.
41
+ */
42
+ export function sleep(seconds) {
43
+ if (!(seconds > 0))
44
+ return sleep0();
45
+ const future = new Future();
46
+ const handle = callLater(seconds, () => {
47
+ if (!future.done())
48
+ future.setResult(undefined);
49
+ });
50
+ const done = awaitFuture(future);
51
+ done.then(() => handle.cancel(), () => handle.cancel());
52
+ return done;
53
+ }
54
+ /** A bare `yield` in a task: reschedule it at the back of the ready queue. */
55
+ function sleep0() {
56
+ const task = currentTask();
57
+ return new Promise((resolve, reject) => {
58
+ callSoon(() => {
59
+ const cancelled = task?._takeCancelRequest();
60
+ if (cancelled)
61
+ reject(cancelled);
62
+ else
63
+ resolve();
64
+ });
65
+ });
66
+ }
67
+ /** asyncio.Event */
68
+ export class Event {
69
+ #value = false;
70
+ #waiters = [];
71
+ isSet() {
72
+ return this.#value;
73
+ }
74
+ set() {
75
+ if (this.#value)
76
+ return;
77
+ this.#value = true;
78
+ for (const future of this.#waiters) {
79
+ if (!future.done())
80
+ future.setResult(true);
81
+ }
82
+ }
83
+ clear() {
84
+ this.#value = false;
85
+ }
86
+ async wait() {
87
+ if (this.#value) {
88
+ currentTask()?.checkpoint();
89
+ return true;
90
+ }
91
+ const future = new Future();
92
+ this.#waiters.push(future);
93
+ try {
94
+ await future;
95
+ return true;
96
+ }
97
+ finally {
98
+ remove(this.#waiters, future);
99
+ }
100
+ }
101
+ }
102
+ export class QueueEmpty extends Error {
103
+ name = "QueueEmpty";
104
+ }
105
+ export class QueueFull extends Error {
106
+ name = "QueueFull";
107
+ }
108
+ export class QueueShutDown extends Error {
109
+ name = "QueueShutDown";
110
+ }
111
+ /**
112
+ * asyncio.Queue. Subclasses override `_init/_put/_get/_size` to change
113
+ * ordering, as Python's PriorityQueue and LifoQueue do.
114
+ */
115
+ export class Queue {
116
+ maxsize;
117
+ #getters = [];
118
+ #putters = [];
119
+ #unfinished = 0;
120
+ #finished = new Event();
121
+ #shutdown = false;
122
+ constructor(maxsize = 0) {
123
+ this.maxsize = maxsize;
124
+ this._init();
125
+ this.#finished.set();
126
+ }
127
+ // Storage hooks. FIFO storage is `_items` from index `_head` on: a deque
128
+ // (collections.deque in Python), since Array.shift() is O(n) on V8.
129
+ _items = [];
130
+ _head = 0;
131
+ _init() {
132
+ this._items = [];
133
+ this._head = 0;
134
+ }
135
+ _put(item) {
136
+ this._items.push(item);
137
+ }
138
+ _get() {
139
+ const items = this._items;
140
+ const item = items[this._head];
141
+ items[this._head] = undefined;
142
+ this._head++;
143
+ if (this._head === items.length) {
144
+ items.length = 0;
145
+ this._head = 0;
146
+ }
147
+ else if (this._head >= 1024 && this._head * 2 >= items.length) {
148
+ items.splice(0, this._head);
149
+ this._head = 0;
150
+ }
151
+ return item;
152
+ }
153
+ _size() {
154
+ return this._items.length - this._head;
155
+ }
156
+ /** The queued items in storage order (for subclasses that inspect them). */
157
+ _queued() {
158
+ return this._head === 0 ? this._items : this._items.slice(this._head);
159
+ }
160
+ #wakeupNext(waiters) {
161
+ while (waiters.length > 0) {
162
+ const waiter = waiters.shift();
163
+ if (!waiter.done()) {
164
+ waiter.setResult(undefined);
165
+ break;
166
+ }
167
+ }
168
+ }
169
+ qsize() {
170
+ return this._size();
171
+ }
172
+ empty() {
173
+ return this._size() === 0;
174
+ }
175
+ full() {
176
+ return this.maxsize > 0 && this._size() >= this.maxsize;
177
+ }
178
+ async put(item) {
179
+ // A cancellation point even when it doesn't block: after an interruption,
180
+ // a task resuming from a foreign await must not push stale items.
181
+ currentTask()?.checkpoint();
182
+ while (this.full()) {
183
+ if (this.#shutdown)
184
+ throw new QueueShutDown();
185
+ const putter = new Future();
186
+ this.#putters.push(putter);
187
+ try {
188
+ await putter;
189
+ }
190
+ catch (e) {
191
+ putter.cancel();
192
+ remove(this.#putters, putter);
193
+ // Woken up by getNowait() but can't take the slot: pass it on.
194
+ if (!this.full() && !putter.cancelled())
195
+ this.#wakeupNext(this.#putters);
196
+ throw e;
197
+ }
198
+ }
199
+ this.putNowait(item);
200
+ }
201
+ putNowait(item) {
202
+ if (this.#shutdown)
203
+ throw new QueueShutDown();
204
+ if (this.full())
205
+ throw new QueueFull();
206
+ this._put(item);
207
+ this.#unfinished++;
208
+ this.#finished.clear();
209
+ this.#wakeupNext(this.#getters);
210
+ }
211
+ async get() {
212
+ if (!this.empty())
213
+ currentTask()?.checkpoint();
214
+ while (this.empty()) {
215
+ if (this.#shutdown && this.empty())
216
+ throw new QueueShutDown();
217
+ const getter = new Future();
218
+ this.#getters.push(getter);
219
+ try {
220
+ await getter;
221
+ }
222
+ catch (e) {
223
+ getter.cancel();
224
+ remove(this.#getters, getter);
225
+ // Woken up by putNowait() but can't take the item: pass it on.
226
+ if (!this.empty() && !getter.cancelled())
227
+ this.#wakeupNext(this.#getters);
228
+ throw e;
229
+ }
230
+ }
231
+ return this.getNowait();
232
+ }
233
+ getNowait() {
234
+ if (this.empty()) {
235
+ if (this.#shutdown)
236
+ throw new QueueShutDown();
237
+ throw new QueueEmpty();
238
+ }
239
+ const item = this._get();
240
+ this.#wakeupNext(this.#putters);
241
+ return item;
242
+ }
243
+ taskDone() {
244
+ if (this.#unfinished <= 0)
245
+ throw new Error("task_done() called too many times");
246
+ this.#unfinished--;
247
+ if (this.#unfinished === 0)
248
+ this.#finished.set();
249
+ }
250
+ async join() {
251
+ if (this.#unfinished > 0)
252
+ await this.#finished.wait();
253
+ }
254
+ shutdown(immediate = false) {
255
+ this.#shutdown = true;
256
+ if (immediate) {
257
+ while (!this.empty()) {
258
+ this._get();
259
+ if (this.#unfinished > 0)
260
+ this.#unfinished--;
261
+ }
262
+ if (this.#unfinished === 0)
263
+ this.#finished.set();
264
+ }
265
+ // All getters need to re-check queue-empty to raise QueueShutDown.
266
+ for (const getter of this.#getters.splice(0)) {
267
+ if (!getter.done())
268
+ getter.setResult(undefined);
269
+ }
270
+ for (const putter of this.#putters.splice(0)) {
271
+ if (!putter.done())
272
+ putter.setResult(undefined);
273
+ }
274
+ }
275
+ }
276
+ /** asyncio.LifoQueue */
277
+ export class LifoQueue extends Queue {
278
+ _get() {
279
+ return this._items.pop();
280
+ }
281
+ }
282
+ /** Default ordering for PriorityQueue: compares numbers, strings and tuples (arrays) like Python. */
283
+ export function pythonCompare(a, b) {
284
+ if (Array.isArray(a) && Array.isArray(b)) {
285
+ const n = Math.min(a.length, b.length);
286
+ for (let i = 0; i < n; i++) {
287
+ const c = pythonCompare(a[i], b[i]);
288
+ if (c !== 0)
289
+ return c;
290
+ }
291
+ return a.length - b.length;
292
+ }
293
+ if (typeof a === "number" && typeof b === "number")
294
+ return a - b;
295
+ if (typeof a === "string" && typeof b === "string")
296
+ return a < b ? -1 : a > b ? 1 : 0;
297
+ if (typeof a === "bigint" && typeof b === "bigint")
298
+ return a < b ? -1 : a > b ? 1 : 0;
299
+ throw new TypeError(`'<' not supported between ${typeof a} and ${typeof b}`);
300
+ }
301
+ /** heapq.heappush */
302
+ export function heapPush(heap, item, compare = pythonCompare) {
303
+ heap.push(item);
304
+ let i = heap.length - 1;
305
+ while (i > 0) {
306
+ const parent = (i - 1) >> 1;
307
+ if (compare(heap[i], heap[parent]) >= 0)
308
+ break;
309
+ [heap[i], heap[parent]] = [heap[parent], heap[i]];
310
+ i = parent;
311
+ }
312
+ }
313
+ /** heapq.heappop */
314
+ export function heapPop(heap, compare = pythonCompare) {
315
+ const top = heap[0];
316
+ const last = heap.pop();
317
+ if (heap.length > 0) {
318
+ heap[0] = last;
319
+ let i = 0;
320
+ for (;;) {
321
+ const l = 2 * i + 1;
322
+ const r = l + 1;
323
+ let m = i;
324
+ if (l < heap.length && compare(heap[l], heap[m]) < 0)
325
+ m = l;
326
+ if (r < heap.length && compare(heap[r], heap[m]) < 0)
327
+ m = r;
328
+ if (m === i)
329
+ break;
330
+ [heap[i], heap[m]] = [heap[m], heap[i]];
331
+ i = m;
332
+ }
333
+ }
334
+ return top;
335
+ }
336
+ /** asyncio.PriorityQueue backed by a binary heap. Smallest item first. */
337
+ export class PriorityQueue extends Queue {
338
+ _compare;
339
+ constructor(maxsize = 0, compare = pythonCompare) {
340
+ super(maxsize);
341
+ this._compare = compare;
342
+ }
343
+ _put(item) {
344
+ heapPush(this._items, item, this._compare);
345
+ }
346
+ _get() {
347
+ return heapPop(this._items, this._compare);
348
+ }
349
+ }
350
+ /** asyncio.Lock */
351
+ export class Lock {
352
+ #locked = false;
353
+ #waiters = null;
354
+ locked() {
355
+ return this.#locked;
356
+ }
357
+ async acquire() {
358
+ if (!this.#locked && (this.#waiters === null || this.#waiters.every((w) => w.cancelled()))) {
359
+ this.#locked = true;
360
+ return true;
361
+ }
362
+ this.#waiters ??= [];
363
+ const future = new Future();
364
+ this.#waiters.push(future);
365
+ try {
366
+ try {
367
+ await future;
368
+ }
369
+ finally {
370
+ remove(this.#waiters, future);
371
+ }
372
+ }
373
+ catch (e) {
374
+ // If the lock is free, make sure the next waiter in line runs.
375
+ if (e instanceof CancelledError && !this.#locked)
376
+ this.#wakeUpFirst();
377
+ throw e;
378
+ }
379
+ this.#locked = true;
380
+ return true;
381
+ }
382
+ release() {
383
+ if (!this.#locked)
384
+ throw new Error("Lock is not acquired.");
385
+ this.#locked = false;
386
+ this.#wakeUpFirst();
387
+ }
388
+ #wakeUpFirst() {
389
+ const first = this.#waiters?.[0];
390
+ // done() means that the waiter is already set to wake up.
391
+ if (first && !first.done())
392
+ first.setResult(true);
393
+ }
394
+ /** `async with lock:` → `await lock.run(async () => {...})` */
395
+ async run(fn) {
396
+ await this.acquire();
397
+ try {
398
+ return await fn();
399
+ }
400
+ finally {
401
+ this.release();
402
+ }
403
+ }
404
+ }
405
+ /** asyncio.Semaphore */
406
+ export class Semaphore {
407
+ #value;
408
+ #waiters = null;
409
+ constructor(value = 1) {
410
+ if (value < 0)
411
+ throw new RangeError("Semaphore initial value must be >= 0");
412
+ this.#value = value;
413
+ }
414
+ /** Due to state, or FIFO rules (must allow others to run first). */
415
+ locked() {
416
+ return this.#value === 0 || (this.#waiters ?? []).some((w) => !w.cancelled());
417
+ }
418
+ async acquire() {
419
+ if (!this.locked()) {
420
+ // Maintain FIFO, wait for others to start even if value > 0.
421
+ this.#value--;
422
+ return true;
423
+ }
424
+ this.#waiters ??= [];
425
+ const future = new Future();
426
+ this.#waiters.push(future);
427
+ try {
428
+ try {
429
+ await future;
430
+ }
431
+ finally {
432
+ remove(this.#waiters, future);
433
+ }
434
+ }
435
+ catch (e) {
436
+ if (e instanceof CancelledError && future.done() && !future.cancelled()) {
437
+ // Woken up by #wakeUpNext() but not acquiring: undo its bookkeeping.
438
+ this.#value++;
439
+ }
440
+ throw e;
441
+ }
442
+ finally {
443
+ // New waiters may have arrived but had to wait due to FIFO.
444
+ while (this.#value > 0) {
445
+ if (!this.#wakeUpNext())
446
+ break;
447
+ }
448
+ }
449
+ return true;
450
+ }
451
+ release() {
452
+ this.#value++;
453
+ this.#wakeUpNext();
454
+ }
455
+ #wakeUpNext() {
456
+ if (!this.#waiters)
457
+ return false;
458
+ for (const future of this.#waiters) {
459
+ if (!future.done()) {
460
+ this.#value--;
461
+ future.setResult(true);
462
+ return true;
463
+ }
464
+ }
465
+ return false;
466
+ }
467
+ async run(fn) {
468
+ await this.acquire();
469
+ try {
470
+ return await fn();
471
+ }
472
+ finally {
473
+ this.release();
474
+ }
475
+ }
476
+ }
477
+ /** asyncio.ensure_future: functions and foreign promises run as tasks. */
478
+ function ensureFuture(aw, name) {
479
+ if (aw instanceof Future)
480
+ return aw;
481
+ if (typeof aw === "function")
482
+ return createTask(aw, name);
483
+ return createTask(() => aw, name);
484
+ }
485
+ /** Await `aw` in the current task, as `await aw` would in Python. */
486
+ function awaitInline(aw) {
487
+ if (aw instanceof Future)
488
+ return awaitFuture(aw);
489
+ if (typeof aw === "function")
490
+ return aw();
491
+ // A foreign promise: make the wait cancellable.
492
+ return abortable(aw);
493
+ }
494
+ /** tasks._cancel_and_wait: cancel `future` and wait until it is done. */
495
+ async function cancelAndWait(future) {
496
+ const waiter = new Future();
497
+ const release = () => {
498
+ if (!waiter.done())
499
+ waiter.setResult(undefined);
500
+ };
501
+ future.addDoneCallback(release);
502
+ try {
503
+ future.cancel();
504
+ // Not waiting on `future` directly keeps this reliably cancellable.
505
+ await waiter;
506
+ }
507
+ finally {
508
+ future.removeDoneCallback(release);
509
+ }
510
+ }
511
+ /**
512
+ * asyncio.timeout(seconds) around `fn`, run in the current task: on expiry
513
+ * the task is cancelled and the CancelledError becomes TimeoutError
514
+ * (timeouts.Timeout). `seconds` null never expires.
515
+ */
516
+ async function withTimeout(seconds, fn) {
517
+ const task = currentTask();
518
+ if (!task) {
519
+ // asyncio.timeout() needs a task; give it one.
520
+ return await createTask(() => withTimeout(seconds, fn), "timeout");
521
+ }
522
+ let expiring = false;
523
+ const cancelling = task.cancelling();
524
+ let handle = null;
525
+ if (seconds !== null && seconds !== undefined) {
526
+ const onTimeout = () => {
527
+ task.cancel();
528
+ expiring = true;
529
+ handle = null;
530
+ };
531
+ handle = seconds <= 0 ? callSoon(onTimeout) : callLater(seconds, onTimeout);
532
+ }
533
+ let result;
534
+ try {
535
+ result = await fn();
536
+ }
537
+ catch (e) {
538
+ handle?.cancel();
539
+ if (expiring && task.uncancel() <= cancelling && e instanceof CancelledError) {
540
+ throw new TimeoutError();
541
+ }
542
+ throw e;
543
+ }
544
+ handle?.cancel();
545
+ if (expiring)
546
+ task.uncancel();
547
+ return result;
548
+ }
549
+ /**
550
+ * asyncio.wait_for(aw, timeout). A function (a "coroutine") or a foreign
551
+ * promise is awaited in the current task, a Task is awaited (and cancelled on
552
+ * timeout). `timeout` null waits forever.
553
+ */
554
+ export async function waitFor(aw, timeout) {
555
+ if (timeout !== null && timeout !== undefined && timeout <= 0) {
556
+ const future = ensureFuture(aw, "wait_for");
557
+ if (future.done())
558
+ return future.result();
559
+ await cancelAndWait(future);
560
+ try {
561
+ return future.result();
562
+ }
563
+ catch (e) {
564
+ if (e instanceof CancelledError)
565
+ throw new TimeoutError();
566
+ throw e;
567
+ }
568
+ }
569
+ return await withTimeout(timeout, () => awaitInline(aw));
570
+ }
571
+ /**
572
+ * asyncio.timeout(seconds) as a scope: runs `fn` and cancels it on timeout,
573
+ * raising TimeoutError.
574
+ */
575
+ export function timeout(seconds, fn) {
576
+ return withTimeout(seconds, fn);
577
+ }
578
+ /** tasks._GatheringFuture: cancelling it cancels the children. */
579
+ class GatheringFuture extends Future {
580
+ #children;
581
+ cancelRequested = false;
582
+ constructor(children) {
583
+ super();
584
+ this.#children = children;
585
+ }
586
+ cancel(message) {
587
+ if (this.done())
588
+ return false;
589
+ let cancelled = false;
590
+ for (const child of this.#children) {
591
+ if (child.cancel(message))
592
+ cancelled = true;
593
+ }
594
+ // Propagate the cancellation once the children are done.
595
+ if (cancelled)
596
+ this.cancelRequested = true;
597
+ return cancelled;
598
+ }
599
+ }
600
+ function gatherFuture(aws, returnExceptions) {
601
+ if (aws.length === 0) {
602
+ const outer = new Future();
603
+ outer.setResult([]);
604
+ return outer;
605
+ }
606
+ const children = [];
607
+ const doneFutures = [];
608
+ const seen = new Map();
609
+ let nfutures = 0;
610
+ let nfinished = 0;
611
+ let outer = null;
612
+ const onDone = (future) => {
613
+ nfinished++;
614
+ if (outer === null || outer.done())
615
+ return;
616
+ if (!returnExceptions) {
617
+ if (future.cancelled()) {
618
+ outer.setException(future._makeCancelledError());
619
+ return;
620
+ }
621
+ const error = future.exception();
622
+ if (error !== undefined) {
623
+ outer.setException(error);
624
+ return;
625
+ }
626
+ }
627
+ if (nfinished === nfutures) {
628
+ const results = children.map((child) => {
629
+ if (child.cancelled())
630
+ return child._makeCancelledError();
631
+ const error = child.exception();
632
+ return error !== undefined ? error : child.result();
633
+ });
634
+ if (outer.cancelRequested)
635
+ outer.setException(future._makeCancelledError());
636
+ else
637
+ outer.setResult(results);
638
+ }
639
+ };
640
+ for (const aw of aws) {
641
+ let future = seen.get(aw);
642
+ if (!future) {
643
+ future = ensureFuture(aw);
644
+ nfutures++;
645
+ seen.set(aw, future);
646
+ if (future.done())
647
+ doneFutures.push(future);
648
+ else
649
+ future.addDoneCallback(onDone);
650
+ }
651
+ children.push(future);
652
+ }
653
+ outer = new GatheringFuture(children);
654
+ for (const future of doneFutures)
655
+ onDone(future);
656
+ return outer;
657
+ }
658
+ /**
659
+ * asyncio.gather(*aws). Functions run as child tasks; if the caller is
660
+ * cancelled, all children are cancelled.
661
+ */
662
+ export function gather(...aws) {
663
+ return awaitFuture(gatherFuture(aws, false));
664
+ }
665
+ /** asyncio.gather(*aws, return_exceptions=True) */
666
+ export function gatherSettled(...aws) {
667
+ return awaitFuture(gatherFuture(aws, true));
668
+ }
669
+ export const FIRST_COMPLETED = "FIRST_COMPLETED";
670
+ export const FIRST_EXCEPTION = "FIRST_EXCEPTION";
671
+ export const ALL_COMPLETED = "ALL_COMPLETED";
672
+ /**
673
+ * asyncio.wait(tasks, timeout, return_when). Does not cancel anything on
674
+ * timeout. Returns [done, pending].
675
+ */
676
+ export async function wait(tasks, options = {}) {
677
+ const all = [...new Set(tasks)];
678
+ const returnWhen = options.returnWhen ?? ALL_COMPLETED;
679
+ if (all.length > 0) {
680
+ const waiter = new Future();
681
+ let timeoutHandle = null;
682
+ if (options.timeout !== undefined && options.timeout !== null) {
683
+ timeoutHandle = callLater(options.timeout, () => {
684
+ if (!waiter.done())
685
+ waiter.setResult(undefined);
686
+ });
687
+ }
688
+ let counter = all.length;
689
+ const onCompletion = (future) => {
690
+ counter--;
691
+ if (counter <= 0 ||
692
+ returnWhen === FIRST_COMPLETED ||
693
+ (returnWhen === FIRST_EXCEPTION && !future.cancelled() && future.exception() !== undefined)) {
694
+ timeoutHandle?.cancel();
695
+ if (!waiter.done())
696
+ waiter.setResult(undefined);
697
+ }
698
+ };
699
+ for (const future of all)
700
+ future.addDoneCallback(onCompletion);
701
+ try {
702
+ await waiter;
703
+ }
704
+ finally {
705
+ timeoutHandle?.cancel();
706
+ for (const future of all)
707
+ future.removeDoneCallback(onCompletion);
708
+ }
709
+ }
710
+ const done = new Set();
711
+ const pending = new Set();
712
+ for (const future of all)
713
+ (future.done() ? done : pending).add(future);
714
+ return [done, pending];
715
+ }
716
+ /** asyncio.shield(aw): the caller can be cancelled without cancelling `aw`. */
717
+ export function shield(aw) {
718
+ const inner = ensureFuture(aw);
719
+ if (inner.done())
720
+ return awaitFuture(inner);
721
+ const outer = new Future();
722
+ const onInnerDone = () => {
723
+ if (outer.cancelled())
724
+ return;
725
+ if (inner.cancelled()) {
726
+ outer.cancel();
727
+ return;
728
+ }
729
+ const error = inner.exception();
730
+ if (error !== undefined)
731
+ outer.setException(error);
732
+ else
733
+ outer.setResult(inner.result());
734
+ };
735
+ inner.addDoneCallback(onInnerDone);
736
+ outer.addDoneCallback(() => {
737
+ if (!inner.done())
738
+ inner.removeDoneCallback(onInnerDone);
739
+ });
740
+ return awaitFuture(outer);
741
+ }
742
+ /** Whether `value` is a Future or Task (asyncio.isfuture). */
743
+ export function isFuture(value) {
744
+ return value instanceof Future;
745
+ }
746
+ //# sourceMappingURL=primitives.js.map