@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.
- package/LICENSE +25 -0
- package/NOTICE +9 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +6 -0
- package/dist/loop.d.ts +26 -0
- package/dist/loop.js +179 -0
- package/dist/primitives.d.ts +138 -0
- package/dist/primitives.js +746 -0
- package/dist/task.d.ts +114 -0
- package/dist/task.js +386 -0
- package/package.json +29 -3
- package/README.md +0 -5
package/dist/task.d.ts
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
export declare class CancelledError extends Error {
|
|
2
|
+
name: string;
|
|
3
|
+
constructor(message?: string);
|
|
4
|
+
}
|
|
5
|
+
export declare class TimeoutError extends Error {
|
|
6
|
+
name: string;
|
|
7
|
+
constructor(message?: string);
|
|
8
|
+
}
|
|
9
|
+
export declare class InvalidStateError extends Error {
|
|
10
|
+
name: string;
|
|
11
|
+
}
|
|
12
|
+
/** Whether `e` is a cancellation. Use it to re-throw in broad `catch` blocks. */
|
|
13
|
+
export declare function isCancelledError(e: unknown): e is CancelledError;
|
|
14
|
+
/**
|
|
15
|
+
* Re-throw `e` if it is a cancellation. Python's `except Exception` does not
|
|
16
|
+
* catch `CancelledError` (it is a BaseException); JavaScript's `catch` catches
|
|
17
|
+
* everything, so ported `except Exception` blocks start with this call.
|
|
18
|
+
*/
|
|
19
|
+
export declare function rethrowIfCancelled(e: unknown): void;
|
|
20
|
+
/** The task the caller is running in, if any (asyncio.current_task). */
|
|
21
|
+
export declare function currentTask(): Task<unknown> | undefined;
|
|
22
|
+
type FutureState = "pending" | "fulfilled" | "rejected" | "cancelled";
|
|
23
|
+
/**
|
|
24
|
+
* asyncio.Future: a result that becomes available later. Awaiting it (it is
|
|
25
|
+
* thenable) suspends the current task until it is done, unless it already is.
|
|
26
|
+
* Done callbacks run on the ready queue, never synchronously.
|
|
27
|
+
*/
|
|
28
|
+
export declare class Future<T = unknown> implements PromiseLike<T> {
|
|
29
|
+
#private;
|
|
30
|
+
done(): boolean;
|
|
31
|
+
cancelled(): boolean;
|
|
32
|
+
result(): T;
|
|
33
|
+
exception(): unknown;
|
|
34
|
+
setResult(value: T): void;
|
|
35
|
+
setException(error: unknown): void;
|
|
36
|
+
/** Cancel the future. Returns false if it is already done. */
|
|
37
|
+
cancel(message?: string): boolean;
|
|
38
|
+
addDoneCallback(callback: (future: this) => void): void;
|
|
39
|
+
removeDoneCallback(callback: (future: this) => void): number;
|
|
40
|
+
then<R1 = T, R2 = never>(onfulfilled?: ((value: T) => R1 | PromiseLike<R1>) | null, onrejected?: ((reason: unknown) => R2 | PromiseLike<R2>) | null): Promise<R1 | R2>;
|
|
41
|
+
/** @internal The CancelledError a cancelled future raises. */
|
|
42
|
+
_makeCancelledError(): CancelledError;
|
|
43
|
+
/** @internal Mark the future done and schedule its callbacks. */
|
|
44
|
+
_setDone(state: Exclude<FutureState, "pending">, value: T | undefined, error: unknown): void;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Await `future` from the current task (Future.__await__ plus the task's
|
|
48
|
+
* wakeup). A done future returns at once, without suspending. Otherwise the
|
|
49
|
+
* task resumes on the ready queue once the future is done, and cancelling
|
|
50
|
+
* the task meanwhile cancels the future.
|
|
51
|
+
*/
|
|
52
|
+
export declare function awaitFuture<T>(future: Future<T>): Promise<T>;
|
|
53
|
+
export type TaskFn<T> = () => Promise<T> | T;
|
|
54
|
+
/**
|
|
55
|
+
* A unit of concurrent work, like asyncio.Task. Created with `createTask()`.
|
|
56
|
+
* Awaiting a Task (it is thenable) is a cancellation point; cancelling the
|
|
57
|
+
* awaiting task also cancels the awaited one, as in asyncio.
|
|
58
|
+
*/
|
|
59
|
+
export declare class Task<T> extends Future<T> {
|
|
60
|
+
#private;
|
|
61
|
+
/** @internal The future this task is suspended on (Task._fut_waiter). */
|
|
62
|
+
_futWaiter: Future<unknown> | null;
|
|
63
|
+
constructor(fn: TaskFn<T>, name?: string);
|
|
64
|
+
get name(): string;
|
|
65
|
+
/** asyncio Task.get_name() */
|
|
66
|
+
getName(): string;
|
|
67
|
+
setName(name: string): void;
|
|
68
|
+
/**
|
|
69
|
+
* Aborted when cancellation is requested. Pass it to fetch() and friends.
|
|
70
|
+
* Once every request is withdrawn with `uncancel()`, a fresh signal is used.
|
|
71
|
+
*/
|
|
72
|
+
get signal(): AbortSignal;
|
|
73
|
+
/** Number of pending cancellation requests (asyncio Task.cancelling()). */
|
|
74
|
+
cancelling(): number;
|
|
75
|
+
uncancel(): number;
|
|
76
|
+
setResult(_value: T): void;
|
|
77
|
+
setException(_error: unknown): void;
|
|
78
|
+
/** Request cancellation. Returns false if the task is already done. */
|
|
79
|
+
cancel(message?: string): boolean;
|
|
80
|
+
/**
|
|
81
|
+
* Throw CancelledError if a cancellation request is pending. Primitives
|
|
82
|
+
* call this on their fast paths, so a task that resumes from a foreign
|
|
83
|
+
* await after being cancelled stops at the next one.
|
|
84
|
+
*/
|
|
85
|
+
checkpoint(): void;
|
|
86
|
+
/** A promise that settles with the task, without being a cancellation point. */
|
|
87
|
+
get promise(): Promise<T>;
|
|
88
|
+
toString(): string;
|
|
89
|
+
/** @internal Consume a pending cancellation request, returning its error. */
|
|
90
|
+
_takeCancelRequest(): CancelledError | undefined;
|
|
91
|
+
/** @internal The task suspends on `future` (Task.__step yielding a future). */
|
|
92
|
+
_block(future: Future<unknown>): void;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Suspend the current task until `register` resolves or rejects, making the
|
|
96
|
+
* wait a cancellation point. `register` returns a disposer that undoes the
|
|
97
|
+
* registration (clear a timer, remove a listener...) when the wait is
|
|
98
|
+
* cancelled. `onCancel` runs after the disposer, e.g. to cancel a child task.
|
|
99
|
+
*/
|
|
100
|
+
export declare function suspend<T>(register: (resolve: (value: T) => void, reject: (error: unknown) => void) => () => void, onCancel?: () => void): Promise<T>;
|
|
101
|
+
/** Create and schedule a task (asyncio.create_task). */
|
|
102
|
+
export declare function createTask<T>(fn: TaskFn<T>, name?: string): Task<T>;
|
|
103
|
+
/** Throw if the current task has a pending cancellation (an explicit `await asyncio.sleep(0)`). */
|
|
104
|
+
export declare function checkCancelled(): void;
|
|
105
|
+
/**
|
|
106
|
+
* Await a foreign promise (fetch, SDK call...) as a cancellation point: if the
|
|
107
|
+
* current task is cancelled while waiting, this rejects with CancelledError.
|
|
108
|
+
* The foreign operation keeps running unless it was also given `task.signal`.
|
|
109
|
+
*/
|
|
110
|
+
export declare function abortable<T>(promise: PromiseLike<T>): Promise<T>;
|
|
111
|
+
/** The current task's abort signal, or a never-aborting one outside tasks. */
|
|
112
|
+
export declare function currentSignal(): AbortSignal;
|
|
113
|
+
export {};
|
|
114
|
+
//# sourceMappingURL=task.d.ts.map
|
package/dist/task.js
ADDED
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
// Copyright (c) 2026, pipedog contributors
|
|
2
|
+
// SPDX-License-Identifier: BSD-2-Clause
|
|
3
|
+
//
|
|
4
|
+
// asyncio-style futures and tasks with cooperative cancellation.
|
|
5
|
+
//
|
|
6
|
+
// Scheduling follows asyncio (see loop.ts): a task runs until it awaits a
|
|
7
|
+
// future that is not done. Completing a future queues its waiters' wakeups on
|
|
8
|
+
// the ready queue instead of resuming them, so every fast path (awaiting a
|
|
9
|
+
// done future, `get()` on a non-empty queue, `put()` on an unbounded one,
|
|
10
|
+
// `wait()` on a set event) completes without letting another task run.
|
|
11
|
+
//
|
|
12
|
+
// Python delivers `CancelledError` *into* a coroutine at the await it is
|
|
13
|
+
// suspended on. JavaScript cannot interrupt an arbitrary await, so we emulate
|
|
14
|
+
// it. Every await on a Future (and so on every primitive in this package:
|
|
15
|
+
// sleep, Event.wait, Queue.get, awaiting a Task, waitFor, gather...) is a
|
|
16
|
+
// "cancellation point". When a task is cancelled:
|
|
17
|
+
//
|
|
18
|
+
// - if it is suspended on a future, that future is cancelled and the task
|
|
19
|
+
// resumes (on the ready queue, as in asyncio) with CancelledError; if it
|
|
20
|
+
// awaits another task, that task is cancelled and this one resumes when it
|
|
21
|
+
// finishes;
|
|
22
|
+
// - otherwise the request stays pending: a task already woken up gets the
|
|
23
|
+
// CancelledError when it resumes, and a task inside a foreign await gets
|
|
24
|
+
// it at the next cancellation point it reaches;
|
|
25
|
+
// - `task.signal` is aborted, so foreign awaits (fetch, WebSocket, SDKs) can
|
|
26
|
+
// be made cancellable by passing the signal or wrapping them in
|
|
27
|
+
// `abortable()`.
|
|
28
|
+
//
|
|
29
|
+
// The current task is tracked with AsyncLocalStorage, which Bun, Node, Deno
|
|
30
|
+
// and Cloudflare Workers (nodejs_compat) all provide.
|
|
31
|
+
/// <reference path="./async-hooks.d.ts" />
|
|
32
|
+
// (The reference keeps the declaration with this file: packages that compile
|
|
33
|
+
// it from source through the workspace exports don't include async-hooks.d.ts.)
|
|
34
|
+
import { callSoon, taskStorage } from "./loop.js";
|
|
35
|
+
export class CancelledError extends Error {
|
|
36
|
+
name = "CancelledError";
|
|
37
|
+
constructor(message = "Task cancelled") {
|
|
38
|
+
super(message);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export class TimeoutError extends Error {
|
|
42
|
+
name = "TimeoutError";
|
|
43
|
+
constructor(message = "Timed out") {
|
|
44
|
+
super(message);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export class InvalidStateError extends Error {
|
|
48
|
+
name = "InvalidStateError";
|
|
49
|
+
}
|
|
50
|
+
/** Whether `e` is a cancellation. Use it to re-throw in broad `catch` blocks. */
|
|
51
|
+
export function isCancelledError(e) {
|
|
52
|
+
return e instanceof CancelledError;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Re-throw `e` if it is a cancellation. Python's `except Exception` does not
|
|
56
|
+
* catch `CancelledError` (it is a BaseException); JavaScript's `catch` catches
|
|
57
|
+
* everything, so ported `except Exception` blocks start with this call.
|
|
58
|
+
*/
|
|
59
|
+
export function rethrowIfCancelled(e) {
|
|
60
|
+
if (e instanceof CancelledError)
|
|
61
|
+
throw e;
|
|
62
|
+
}
|
|
63
|
+
/** The task the caller is running in, if any (asyncio.current_task). */
|
|
64
|
+
export function currentTask() {
|
|
65
|
+
return taskStorage.getStore();
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* asyncio.Future: a result that becomes available later. Awaiting it (it is
|
|
69
|
+
* thenable) suspends the current task until it is done, unless it already is.
|
|
70
|
+
* Done callbacks run on the ready queue, never synchronously.
|
|
71
|
+
*/
|
|
72
|
+
export class Future {
|
|
73
|
+
#state = "pending";
|
|
74
|
+
#result;
|
|
75
|
+
#error;
|
|
76
|
+
#cancelMessage;
|
|
77
|
+
#callbacks = [];
|
|
78
|
+
done() {
|
|
79
|
+
return this.#state !== "pending";
|
|
80
|
+
}
|
|
81
|
+
cancelled() {
|
|
82
|
+
return this.#state === "cancelled";
|
|
83
|
+
}
|
|
84
|
+
result() {
|
|
85
|
+
if (this.#state === "pending")
|
|
86
|
+
throw new InvalidStateError("Result is not set.");
|
|
87
|
+
if (this.#state === "fulfilled")
|
|
88
|
+
return this.#result;
|
|
89
|
+
if (this.#state === "cancelled")
|
|
90
|
+
throw this._makeCancelledError();
|
|
91
|
+
throw this.#error;
|
|
92
|
+
}
|
|
93
|
+
exception() {
|
|
94
|
+
if (this.#state === "pending")
|
|
95
|
+
throw new InvalidStateError("Exception is not set.");
|
|
96
|
+
if (this.#state === "cancelled")
|
|
97
|
+
throw this._makeCancelledError();
|
|
98
|
+
return this.#state === "rejected" ? this.#error : undefined;
|
|
99
|
+
}
|
|
100
|
+
setResult(value) {
|
|
101
|
+
if (this.#state !== "pending")
|
|
102
|
+
throw new InvalidStateError(`invalid state: ${this.#state}`);
|
|
103
|
+
this._setDone("fulfilled", value, undefined);
|
|
104
|
+
}
|
|
105
|
+
setException(error) {
|
|
106
|
+
if (this.#state !== "pending")
|
|
107
|
+
throw new InvalidStateError(`invalid state: ${this.#state}`);
|
|
108
|
+
this._setDone("rejected", undefined, error);
|
|
109
|
+
}
|
|
110
|
+
/** Cancel the future. Returns false if it is already done. */
|
|
111
|
+
cancel(message) {
|
|
112
|
+
if (this.#state !== "pending")
|
|
113
|
+
return false;
|
|
114
|
+
this.#cancelMessage = message;
|
|
115
|
+
this._setDone("cancelled", undefined, undefined);
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
addDoneCallback(callback) {
|
|
119
|
+
if (this.#state !== "pending") {
|
|
120
|
+
callSoon(() => callback(this));
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
this.#callbacks.push(callback);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
removeDoneCallback(callback) {
|
|
127
|
+
const before = this.#callbacks.length;
|
|
128
|
+
this.#callbacks = this.#callbacks.filter((c) => c !== callback);
|
|
129
|
+
return before - this.#callbacks.length;
|
|
130
|
+
}
|
|
131
|
+
// biome-ignore lint/suspicious/noThenProperty: Futures are awaitable by design, like asyncio.
|
|
132
|
+
then(onfulfilled, onrejected) {
|
|
133
|
+
return awaitFuture(this).then(onfulfilled, onrejected);
|
|
134
|
+
}
|
|
135
|
+
/** @internal The CancelledError a cancelled future raises. */
|
|
136
|
+
_makeCancelledError() {
|
|
137
|
+
if (this.#error instanceof CancelledError)
|
|
138
|
+
return this.#error;
|
|
139
|
+
return new CancelledError(this.#cancelMessage);
|
|
140
|
+
}
|
|
141
|
+
/** @internal Mark the future done and schedule its callbacks. */
|
|
142
|
+
_setDone(state, value, error) {
|
|
143
|
+
this.#state = state;
|
|
144
|
+
this.#result = value;
|
|
145
|
+
this.#error = error;
|
|
146
|
+
const callbacks = this.#callbacks;
|
|
147
|
+
this.#callbacks = [];
|
|
148
|
+
for (const callback of callbacks)
|
|
149
|
+
callSoon(() => callback(this));
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Await `future` from the current task (Future.__await__ plus the task's
|
|
154
|
+
* wakeup). A done future returns at once, without suspending. Otherwise the
|
|
155
|
+
* task resumes on the ready queue once the future is done, and cancelling
|
|
156
|
+
* the task meanwhile cancels the future.
|
|
157
|
+
*/
|
|
158
|
+
export function awaitFuture(future) {
|
|
159
|
+
if (future.done()) {
|
|
160
|
+
try {
|
|
161
|
+
return Promise.resolve(future.result());
|
|
162
|
+
}
|
|
163
|
+
catch (e) {
|
|
164
|
+
return Promise.reject(e);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
const task = currentTask();
|
|
168
|
+
return new Promise((resolve, reject) => {
|
|
169
|
+
future.addDoneCallback(() => {
|
|
170
|
+
if (task) {
|
|
171
|
+
task._futWaiter = null;
|
|
172
|
+
// A cancellation requested after the wakeup was queued wins (Task.__step).
|
|
173
|
+
const cancelled = task._takeCancelRequest();
|
|
174
|
+
if (cancelled) {
|
|
175
|
+
reject(cancelled);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
try {
|
|
180
|
+
resolve(future.result());
|
|
181
|
+
}
|
|
182
|
+
catch (e) {
|
|
183
|
+
reject(e);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
task?._block(future);
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
let taskCounter = 0;
|
|
190
|
+
/**
|
|
191
|
+
* A unit of concurrent work, like asyncio.Task. Created with `createTask()`.
|
|
192
|
+
* Awaiting a Task (it is thenable) is a cancellation point; cancelling the
|
|
193
|
+
* awaiting task also cancels the awaited one, as in asyncio.
|
|
194
|
+
*/
|
|
195
|
+
export class Task extends Future {
|
|
196
|
+
#controller = new AbortController();
|
|
197
|
+
#name;
|
|
198
|
+
#mustCancel = false;
|
|
199
|
+
#cancelMessage;
|
|
200
|
+
#cancelRequests = 0;
|
|
201
|
+
#promise;
|
|
202
|
+
/** @internal The future this task is suspended on (Task._fut_waiter). */
|
|
203
|
+
_futWaiter = null;
|
|
204
|
+
constructor(fn, name) {
|
|
205
|
+
super();
|
|
206
|
+
this.#name = name ?? `Task-${++taskCounter}`;
|
|
207
|
+
let start;
|
|
208
|
+
const started = new Promise((resolve, reject) => {
|
|
209
|
+
start = { resolve, reject };
|
|
210
|
+
});
|
|
211
|
+
// Like asyncio, a task never starts running synchronously in the caller:
|
|
212
|
+
// its first step is scheduled on the ready queue.
|
|
213
|
+
callSoon(() => {
|
|
214
|
+
const cancelled = this._takeCancelRequest();
|
|
215
|
+
if (cancelled)
|
|
216
|
+
start.reject(cancelled);
|
|
217
|
+
else
|
|
218
|
+
start.resolve();
|
|
219
|
+
});
|
|
220
|
+
taskStorage
|
|
221
|
+
.run(this, async () => {
|
|
222
|
+
await started;
|
|
223
|
+
return await fn();
|
|
224
|
+
})
|
|
225
|
+
.then((value) => {
|
|
226
|
+
// Cancelled right before returning: the task ends cancelled.
|
|
227
|
+
const cancelled = this._takeCancelRequest();
|
|
228
|
+
if (cancelled)
|
|
229
|
+
this._setDone("cancelled", undefined, cancelled);
|
|
230
|
+
else
|
|
231
|
+
this._setDone("fulfilled", value, undefined);
|
|
232
|
+
}, (error) => this._setDone(error instanceof CancelledError ? "cancelled" : "rejected", undefined, error));
|
|
233
|
+
}
|
|
234
|
+
get name() {
|
|
235
|
+
return this.#name;
|
|
236
|
+
}
|
|
237
|
+
/** asyncio Task.get_name() */
|
|
238
|
+
getName() {
|
|
239
|
+
return this.#name;
|
|
240
|
+
}
|
|
241
|
+
setName(name) {
|
|
242
|
+
this.#name = name;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Aborted when cancellation is requested. Pass it to fetch() and friends.
|
|
246
|
+
* Once every request is withdrawn with `uncancel()`, a fresh signal is used.
|
|
247
|
+
*/
|
|
248
|
+
get signal() {
|
|
249
|
+
return this.#controller.signal;
|
|
250
|
+
}
|
|
251
|
+
/** Number of pending cancellation requests (asyncio Task.cancelling()). */
|
|
252
|
+
cancelling() {
|
|
253
|
+
return this.#cancelRequests;
|
|
254
|
+
}
|
|
255
|
+
uncancel() {
|
|
256
|
+
if (this.#cancelRequests > 0)
|
|
257
|
+
this.#cancelRequests--;
|
|
258
|
+
if (this.#cancelRequests === 0) {
|
|
259
|
+
this.#mustCancel = false;
|
|
260
|
+
if (this.#controller.signal.aborted)
|
|
261
|
+
this.#controller = new AbortController();
|
|
262
|
+
}
|
|
263
|
+
return this.#cancelRequests;
|
|
264
|
+
}
|
|
265
|
+
setResult(_value) {
|
|
266
|
+
throw new InvalidStateError("Task does not support setResult()");
|
|
267
|
+
}
|
|
268
|
+
setException(_error) {
|
|
269
|
+
throw new InvalidStateError("Task does not support setException()");
|
|
270
|
+
}
|
|
271
|
+
/** Request cancellation. Returns false if the task is already done. */
|
|
272
|
+
cancel(message) {
|
|
273
|
+
if (this.done())
|
|
274
|
+
return false;
|
|
275
|
+
this.#cancelRequests++;
|
|
276
|
+
if (!this.#controller.signal.aborted) {
|
|
277
|
+
this.#controller.abort(new CancelledError(message));
|
|
278
|
+
}
|
|
279
|
+
// Cancel what the task is suspended on; it resumes with the outcome.
|
|
280
|
+
if (this._futWaiter?.cancel(message))
|
|
281
|
+
return true;
|
|
282
|
+
// Running, woken up but not resumed yet, or inside a foreign await.
|
|
283
|
+
this.#mustCancel = true;
|
|
284
|
+
this.#cancelMessage = message;
|
|
285
|
+
return true;
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Throw CancelledError if a cancellation request is pending. Primitives
|
|
289
|
+
* call this on their fast paths, so a task that resumes from a foreign
|
|
290
|
+
* await after being cancelled stops at the next one.
|
|
291
|
+
*/
|
|
292
|
+
checkpoint() {
|
|
293
|
+
const cancelled = this._takeCancelRequest();
|
|
294
|
+
if (cancelled)
|
|
295
|
+
throw cancelled;
|
|
296
|
+
}
|
|
297
|
+
/** A promise that settles with the task, without being a cancellation point. */
|
|
298
|
+
get promise() {
|
|
299
|
+
if (!this.#promise) {
|
|
300
|
+
this.#promise = new Promise((resolve, reject) => {
|
|
301
|
+
this.addDoneCallback(() => {
|
|
302
|
+
try {
|
|
303
|
+
resolve(this.result());
|
|
304
|
+
}
|
|
305
|
+
catch (e) {
|
|
306
|
+
reject(e);
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
// Errors are observed through the task; never an unhandled rejection.
|
|
311
|
+
this.#promise.catch(() => { });
|
|
312
|
+
}
|
|
313
|
+
return this.#promise;
|
|
314
|
+
}
|
|
315
|
+
toString() {
|
|
316
|
+
return this.#name;
|
|
317
|
+
}
|
|
318
|
+
/** @internal Consume a pending cancellation request, returning its error. */
|
|
319
|
+
_takeCancelRequest() {
|
|
320
|
+
if (!this.#mustCancel)
|
|
321
|
+
return undefined;
|
|
322
|
+
this.#mustCancel = false;
|
|
323
|
+
return new CancelledError(this.#cancelMessage);
|
|
324
|
+
}
|
|
325
|
+
/** @internal The task suspends on `future` (Task.__step yielding a future). */
|
|
326
|
+
_block(future) {
|
|
327
|
+
this._futWaiter = future;
|
|
328
|
+
if (this.#mustCancel && future.cancel(this.#cancelMessage))
|
|
329
|
+
this.#mustCancel = false;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Suspend the current task until `register` resolves or rejects, making the
|
|
334
|
+
* wait a cancellation point. `register` returns a disposer that undoes the
|
|
335
|
+
* registration (clear a timer, remove a listener...) when the wait is
|
|
336
|
+
* cancelled. `onCancel` runs after the disposer, e.g. to cancel a child task.
|
|
337
|
+
*/
|
|
338
|
+
export async function suspend(register, onCancel) {
|
|
339
|
+
const future = new Future();
|
|
340
|
+
const dispose = register((value) => {
|
|
341
|
+
if (!future.done())
|
|
342
|
+
future.setResult(value);
|
|
343
|
+
}, (error) => {
|
|
344
|
+
if (!future.done())
|
|
345
|
+
future.setException(error);
|
|
346
|
+
});
|
|
347
|
+
try {
|
|
348
|
+
return await awaitFuture(future);
|
|
349
|
+
}
|
|
350
|
+
catch (e) {
|
|
351
|
+
if (future.cancelled()) {
|
|
352
|
+
dispose();
|
|
353
|
+
onCancel?.();
|
|
354
|
+
}
|
|
355
|
+
throw e;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
/** Create and schedule a task (asyncio.create_task). */
|
|
359
|
+
export function createTask(fn, name) {
|
|
360
|
+
return new Task(fn, name);
|
|
361
|
+
}
|
|
362
|
+
/** Throw if the current task has a pending cancellation (an explicit `await asyncio.sleep(0)`). */
|
|
363
|
+
export function checkCancelled() {
|
|
364
|
+
currentTask()?.checkpoint();
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Await a foreign promise (fetch, SDK call...) as a cancellation point: if the
|
|
368
|
+
* current task is cancelled while waiting, this rejects with CancelledError.
|
|
369
|
+
* The foreign operation keeps running unless it was also given `task.signal`.
|
|
370
|
+
*/
|
|
371
|
+
export function abortable(promise) {
|
|
372
|
+
const future = new Future();
|
|
373
|
+
promise.then((value) => {
|
|
374
|
+
if (!future.done())
|
|
375
|
+
future.setResult(value);
|
|
376
|
+
}, (error) => {
|
|
377
|
+
if (!future.done())
|
|
378
|
+
future.setException(error);
|
|
379
|
+
});
|
|
380
|
+
return awaitFuture(future);
|
|
381
|
+
}
|
|
382
|
+
/** The current task's abort signal, or a never-aborting one outside tasks. */
|
|
383
|
+
export function currentSignal() {
|
|
384
|
+
return currentTask()?.signal ?? new AbortController().signal;
|
|
385
|
+
}
|
|
386
|
+
//# sourceMappingURL=task.js.map
|
package/package.json
CHANGED
|
@@ -1,12 +1,38 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pipedog/asyncio",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "asyncio-style tasks, cancellation, queues and events for JavaScript runtimes (Bun, Node, Workers).",
|
|
5
5
|
"license": "BSD-2-Clause",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "git+https://github.com/polvallverdu/pipedog.git",
|
|
9
9
|
"directory": "packages/asyncio"
|
|
10
10
|
},
|
|
11
|
-
"homepage": "https://github.com/polvallverdu/pipedog/tree/main/packages/asyncio#readme"
|
|
11
|
+
"homepage": "https://github.com/polvallverdu/pipedog/tree/main/packages/asyncio#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/polvallverdu/pipedog/issues"
|
|
14
|
+
},
|
|
15
|
+
"type": "module",
|
|
16
|
+
"sideEffects": false,
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"default": "./dist/index.js"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist",
|
|
25
|
+
"LICENSE",
|
|
26
|
+
"NOTICE"
|
|
27
|
+
],
|
|
28
|
+
"pipecat": {
|
|
29
|
+
"runtimes": [
|
|
30
|
+
"bun",
|
|
31
|
+
"node",
|
|
32
|
+
"deno",
|
|
33
|
+
"workerd"
|
|
34
|
+
]
|
|
35
|
+
},
|
|
36
|
+
"main": "./dist/index.js",
|
|
37
|
+
"types": "./dist/index.d.ts"
|
|
12
38
|
}
|