als-browser 1.0.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/.claude/settings.local.json +10 -0
- package/README.md +257 -0
- package/package.json +40 -0
- package/src/async-context-frame.ts +53 -0
- package/src/async-local-storage.test.ts +264 -0
- package/src/async-local-storage.ts +111 -0
- package/src/index.ts +8 -0
- package/src/patches/event-target.test.ts +482 -0
- package/src/patches/event-target.ts +100 -0
- package/src/patches/index.ts +57 -0
- package/src/patches/microtasks.test.ts +232 -0
- package/src/patches/microtasks.ts +31 -0
- package/src/patches/observers.test.ts +594 -0
- package/src/patches/observers.ts +112 -0
- package/src/patches/patch-helper.ts +73 -0
- package/src/patches/promise.test.ts +355 -0
- package/src/patches/promise.ts +83 -0
- package/src/patches/timers.test.ts +321 -0
- package/src/patches/timers.ts +109 -0
- package/src/patches/xhr.test.ts +81 -0
- package/src/patches/xhr.ts +58 -0
- package/src/snapshot.test.ts +66 -0
- package/src/snapshot.ts +38 -0
- package/tsconfig.json +19 -0
- package/tsup.config.ts +12 -0
- package/vitest.config.ts +8 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility for managing patches with Symbol-based marking
|
|
3
|
+
* to prevent double-patching and enable reversal
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const ORIGINAL_SYMBOL = Symbol.for("als-browser:original");
|
|
7
|
+
|
|
8
|
+
export interface PatchTarget {
|
|
9
|
+
[key: string | symbol]: any;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Check if a target's property has been patched
|
|
14
|
+
*/
|
|
15
|
+
export function isPatched(
|
|
16
|
+
target: PatchTarget,
|
|
17
|
+
property: string | symbol
|
|
18
|
+
): boolean {
|
|
19
|
+
const current = target[property];
|
|
20
|
+
return current && typeof current === "object" && ORIGINAL_SYMBOL in current;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Get the original implementation of a patched property
|
|
25
|
+
*/
|
|
26
|
+
export function getOriginal<T>(
|
|
27
|
+
target: PatchTarget,
|
|
28
|
+
property: string | symbol
|
|
29
|
+
): T | undefined {
|
|
30
|
+
const current = target[property];
|
|
31
|
+
return current?.[ORIGINAL_SYMBOL];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Apply a patch to a property, storing the original on the patched value
|
|
36
|
+
*/
|
|
37
|
+
export function patch<T>(
|
|
38
|
+
target: PatchTarget,
|
|
39
|
+
property: string | symbol,
|
|
40
|
+
patcher: (original: T) => T
|
|
41
|
+
): void {
|
|
42
|
+
// Check if already patched
|
|
43
|
+
if (isPatched(target, property)) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Store the original
|
|
48
|
+
const original = target[property] as T;
|
|
49
|
+
|
|
50
|
+
// Apply the patch
|
|
51
|
+
const patched = patcher(original);
|
|
52
|
+
|
|
53
|
+
// Store the original on the patched value
|
|
54
|
+
(patched as any)[ORIGINAL_SYMBOL] = original;
|
|
55
|
+
|
|
56
|
+
target[property] = patched as any;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Reverse a patch, restoring the original implementation
|
|
61
|
+
*/
|
|
62
|
+
export function unpatch(
|
|
63
|
+
target: PatchTarget,
|
|
64
|
+
property: string | symbol
|
|
65
|
+
): boolean {
|
|
66
|
+
const original = getOriginal(target, property);
|
|
67
|
+
if (original === undefined) {
|
|
68
|
+
return false; // Not patched
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
target[property] = original;
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from "vitest";
|
|
2
|
+
import { AsyncLocalStorage } from "../async-local-storage";
|
|
3
|
+
import { AsyncContextFrame } from "../async-context-frame";
|
|
4
|
+
import { patchPromise } from "./promise";
|
|
5
|
+
|
|
6
|
+
describe("Promise patch", () => {
|
|
7
|
+
let als: AsyncLocalStorage<number>;
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
AsyncContextFrame.set(undefined);
|
|
11
|
+
als = new AsyncLocalStorage<number>();
|
|
12
|
+
patchPromise();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
describe("Promise.then()", () => {
|
|
16
|
+
it("should preserve context through Promise.then()", async () => {
|
|
17
|
+
const result = await als.run(100, () => {
|
|
18
|
+
return Promise.resolve(42).then((value) => {
|
|
19
|
+
return { value, store: als.getStore() };
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
expect(result).toEqual({ value: 42, store: 100 });
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("should preserve context through chained .then() calls", async () => {
|
|
27
|
+
const stores: (number | undefined)[] = [];
|
|
28
|
+
|
|
29
|
+
const result = await als.run(200, () => {
|
|
30
|
+
return Promise.resolve(1)
|
|
31
|
+
.then((value) => {
|
|
32
|
+
stores.push(als.getStore());
|
|
33
|
+
return value + 1;
|
|
34
|
+
})
|
|
35
|
+
.then((value) => {
|
|
36
|
+
stores.push(als.getStore());
|
|
37
|
+
return value + 1;
|
|
38
|
+
})
|
|
39
|
+
.then((value) => {
|
|
40
|
+
stores.push(als.getStore());
|
|
41
|
+
return value + 1;
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
expect(result).toBe(4);
|
|
46
|
+
expect(stores).toEqual([200, 200, 200]);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("should preserve context through .then() with both callbacks", async () => {
|
|
50
|
+
const fulfilledStore = await als.run(300, () => {
|
|
51
|
+
return Promise.resolve(42).then(
|
|
52
|
+
() => als.getStore(),
|
|
53
|
+
() => undefined
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
expect(fulfilledStore).toBe(300);
|
|
57
|
+
|
|
58
|
+
const rejectedStore = await als.run(400, () => {
|
|
59
|
+
return Promise.reject(new Error("test")).then(
|
|
60
|
+
() => undefined,
|
|
61
|
+
() => als.getStore()
|
|
62
|
+
);
|
|
63
|
+
});
|
|
64
|
+
expect(rejectedStore).toBe(400);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("should preserve context when returning a Promise from .then()", async () => {
|
|
68
|
+
const result = await als.run(500, () => {
|
|
69
|
+
return Promise.resolve(1).then(() => {
|
|
70
|
+
return Promise.resolve(2).then(() => {
|
|
71
|
+
return als.getStore();
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
expect(result).toBe(500);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("should preserve context when .then() is called without callbacks", async () => {
|
|
80
|
+
const result = await als.run(600, () => {
|
|
81
|
+
return Promise.resolve(42).then().then((value) => {
|
|
82
|
+
return { value, store: als.getStore() };
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
expect(result).toEqual({ value: 42, store: 600 });
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("should preserve context through .then() on rejected promise", async () => {
|
|
90
|
+
const result = await als.run(700, () => {
|
|
91
|
+
return Promise.reject(new Error("test")).then(null, (err) => {
|
|
92
|
+
return { error: err.message, store: als.getStore() };
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
expect(result).toEqual({ error: "test", store: 700 });
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("should isolate contexts in parallel .then() calls", async () => {
|
|
100
|
+
const results: number[] = [];
|
|
101
|
+
|
|
102
|
+
const promises = [1, 2, 3].map((value) => {
|
|
103
|
+
return als.run(value, () => {
|
|
104
|
+
return Promise.resolve().then(() => {
|
|
105
|
+
results.push(als.getStore()!);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
await Promise.all(promises);
|
|
111
|
+
expect(results.sort()).toEqual([1, 2, 3]);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe("Promise.catch()", () => {
|
|
116
|
+
it("should preserve context through Promise.catch()", async () => {
|
|
117
|
+
const result = await als.run(800, () => {
|
|
118
|
+
return Promise.reject(new Error("test")).catch((err) => {
|
|
119
|
+
return { error: err.message, store: als.getStore() };
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
expect(result).toEqual({ error: "test", store: 800 });
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("should preserve context through chained .catch() calls", async () => {
|
|
127
|
+
const stores: (number | undefined)[] = [];
|
|
128
|
+
|
|
129
|
+
const result = await als.run(900, () => {
|
|
130
|
+
return Promise.reject(new Error("first"))
|
|
131
|
+
.catch((err) => {
|
|
132
|
+
stores.push(als.getStore());
|
|
133
|
+
throw new Error("second");
|
|
134
|
+
})
|
|
135
|
+
.catch((err) => {
|
|
136
|
+
stores.push(als.getStore());
|
|
137
|
+
return "recovered";
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
expect(result).toBe("recovered");
|
|
142
|
+
expect(stores).toEqual([900, 900]);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("should preserve context when .catch() is called on fulfilled promise", async () => {
|
|
146
|
+
const result = await als.run(1000, () => {
|
|
147
|
+
return Promise.resolve(42)
|
|
148
|
+
.catch(() => {
|
|
149
|
+
// Should not be called
|
|
150
|
+
return 0;
|
|
151
|
+
})
|
|
152
|
+
.then((value) => {
|
|
153
|
+
return { value, store: als.getStore() };
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
expect(result).toEqual({ value: 42, store: 1000 });
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("should preserve context when returning a Promise from .catch()", async () => {
|
|
161
|
+
const result = await als.run(1100, () => {
|
|
162
|
+
return Promise.reject(new Error("test")).catch(() => {
|
|
163
|
+
return Promise.resolve(als.getStore());
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
expect(result).toBe(1100);
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
describe("Promise.finally()", () => {
|
|
172
|
+
it("should preserve context through Promise.finally() on fulfilled promise", async () => {
|
|
173
|
+
let finallyStore: number | undefined;
|
|
174
|
+
|
|
175
|
+
const result = await als.run(1200, () => {
|
|
176
|
+
return Promise.resolve(42).finally(() => {
|
|
177
|
+
finallyStore = als.getStore();
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
expect(result).toBe(42);
|
|
182
|
+
expect(finallyStore).toBe(1200);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("should preserve context through Promise.finally() on rejected promise", async () => {
|
|
186
|
+
let finallyStore: number | undefined;
|
|
187
|
+
|
|
188
|
+
const result = await als.run(1300, () => {
|
|
189
|
+
return Promise.reject(new Error("test"))
|
|
190
|
+
.finally(() => {
|
|
191
|
+
finallyStore = als.getStore();
|
|
192
|
+
})
|
|
193
|
+
.catch(() => "recovered");
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
expect(result).toBe("recovered");
|
|
197
|
+
expect(finallyStore).toBe(1300);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it("should preserve context through chained .finally() calls", async () => {
|
|
201
|
+
const stores: (number | undefined)[] = [];
|
|
202
|
+
|
|
203
|
+
const result = await als.run(1400, () => {
|
|
204
|
+
return Promise.resolve(42)
|
|
205
|
+
.finally(() => {
|
|
206
|
+
stores.push(als.getStore());
|
|
207
|
+
})
|
|
208
|
+
.finally(() => {
|
|
209
|
+
stores.push(als.getStore());
|
|
210
|
+
})
|
|
211
|
+
.finally(() => {
|
|
212
|
+
stores.push(als.getStore());
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
expect(result).toBe(42);
|
|
217
|
+
expect(stores).toEqual([1400, 1400, 1400]);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it("should preserve context when .finally() is combined with .then()", async () => {
|
|
221
|
+
const sequence: string[] = [];
|
|
222
|
+
|
|
223
|
+
const result = await als.run(1500, () => {
|
|
224
|
+
return Promise.resolve(1)
|
|
225
|
+
.then((value) => {
|
|
226
|
+
sequence.push(`then:${als.getStore()}`);
|
|
227
|
+
return value + 1;
|
|
228
|
+
})
|
|
229
|
+
.finally(() => {
|
|
230
|
+
sequence.push(`finally:${als.getStore()}`);
|
|
231
|
+
})
|
|
232
|
+
.then((value) => {
|
|
233
|
+
sequence.push(`then2:${als.getStore()}`);
|
|
234
|
+
return value + 1;
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
expect(result).toBe(3);
|
|
239
|
+
expect(sequence).toEqual(["then:1500", "finally:1500", "then2:1500"]);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it("should preserve context when .finally() returns a promise", async () => {
|
|
243
|
+
let finallyStore: number | undefined;
|
|
244
|
+
|
|
245
|
+
const result = await als.run(1600, () => {
|
|
246
|
+
return Promise.resolve(42).finally(() => {
|
|
247
|
+
return Promise.resolve().then(() => {
|
|
248
|
+
finallyStore = als.getStore();
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
expect(result).toBe(42);
|
|
254
|
+
expect(finallyStore).toBe(1600);
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
describe("Promise combinations", () => {
|
|
259
|
+
it("should preserve context through .then().catch().finally()", async () => {
|
|
260
|
+
const sequence: string[] = [];
|
|
261
|
+
|
|
262
|
+
const result = await als.run(1700, () => {
|
|
263
|
+
return Promise.reject(new Error("test"))
|
|
264
|
+
.then(() => {
|
|
265
|
+
sequence.push("then (should not run)");
|
|
266
|
+
})
|
|
267
|
+
.catch((err) => {
|
|
268
|
+
sequence.push(`catch:${als.getStore()}`);
|
|
269
|
+
return "recovered";
|
|
270
|
+
})
|
|
271
|
+
.finally(() => {
|
|
272
|
+
sequence.push(`finally:${als.getStore()}`);
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
expect(result).toBe("recovered");
|
|
277
|
+
expect(sequence).toEqual(["catch:1700", "finally:1700"]);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it("should preserve context in nested promise chains", async () => {
|
|
281
|
+
const result = await als.run(1800, () => {
|
|
282
|
+
return Promise.resolve(1)
|
|
283
|
+
.then((value) => {
|
|
284
|
+
return Promise.resolve(value + 1).then((nested) => {
|
|
285
|
+
return Promise.resolve(nested + 1).then((doubleNested) => {
|
|
286
|
+
return { value: doubleNested, store: als.getStore() };
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
expect(result).toEqual({ value: 3, store: 1800 });
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it("should preserve context when promises are created outside run()", async () => {
|
|
296
|
+
const promise = Promise.resolve(42);
|
|
297
|
+
|
|
298
|
+
const result = await als.run(1900, () => {
|
|
299
|
+
return promise.then((value) => {
|
|
300
|
+
return { value, store: als.getStore() };
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
expect(result).toEqual({ value: 42, store: 1900 });
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it("should preserve context through Promise.all()", async () => {
|
|
308
|
+
const result = await als.run(2000, () => {
|
|
309
|
+
return Promise.all([
|
|
310
|
+
Promise.resolve(1).then(() => als.getStore()),
|
|
311
|
+
Promise.resolve(2).then(() => als.getStore()),
|
|
312
|
+
Promise.resolve(3).then(() => als.getStore()),
|
|
313
|
+
]);
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
expect(result).toEqual([2000, 2000, 2000]);
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it("should preserve context through Promise.race()", async () => {
|
|
320
|
+
const result = await als.run(2100, () => {
|
|
321
|
+
return Promise.race([
|
|
322
|
+
Promise.resolve(1).then(() => als.getStore()),
|
|
323
|
+
Promise.resolve(2).then(() => als.getStore()),
|
|
324
|
+
]);
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
expect(result).toBe(2100);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it("should preserve context through Promise.allSettled()", async () => {
|
|
331
|
+
const result = await als.run(2200, () => {
|
|
332
|
+
return Promise.allSettled([
|
|
333
|
+
Promise.resolve(1).then(() => als.getStore()),
|
|
334
|
+
Promise.reject(new Error("test")).catch(() => als.getStore()),
|
|
335
|
+
]);
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
expect(result).toEqual([
|
|
339
|
+
{ status: "fulfilled", value: 2200 },
|
|
340
|
+
{ status: "fulfilled", value: 2200 },
|
|
341
|
+
]);
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
it("should preserve context through Promise.any()", async () => {
|
|
345
|
+
const result = await als.run(2300, () => {
|
|
346
|
+
return Promise.any([
|
|
347
|
+
Promise.reject(new Error("test")),
|
|
348
|
+
Promise.resolve(1).then(() => als.getStore()),
|
|
349
|
+
]);
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
expect(result).toBe(2300);
|
|
353
|
+
});
|
|
354
|
+
});
|
|
355
|
+
});
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "../async-local-storage";
|
|
2
|
+
import { patch, unpatch } from "./patch-helper";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Patch Promise continuation methods to preserve async context.
|
|
6
|
+
* This ensures that callbacks passed to .then(), .catch(), and .finally()
|
|
7
|
+
* maintain their async context when they execute.
|
|
8
|
+
*
|
|
9
|
+
* Note: This patches the continuation methods, not `await` itself.
|
|
10
|
+
* Using `await` requires code transformation via capture/restore.
|
|
11
|
+
*/
|
|
12
|
+
export function patchPromise(): void {
|
|
13
|
+
// Patch Promise.prototype.then
|
|
14
|
+
patch(
|
|
15
|
+
Promise.prototype as any,
|
|
16
|
+
"then",
|
|
17
|
+
(original: typeof Promise.prototype.then) => {
|
|
18
|
+
return function <T, R1 = T, R2 = never>(
|
|
19
|
+
this: Promise<T>,
|
|
20
|
+
onFulfilled?:
|
|
21
|
+
| ((value: T) => R1 | PromiseLike<R1>)
|
|
22
|
+
| undefined
|
|
23
|
+
| null,
|
|
24
|
+
onRejected?: ((reason: any) => R2 | PromiseLike<R2>) | undefined | null
|
|
25
|
+
): Promise<R1 | R2> {
|
|
26
|
+
const boundFulfilled = onFulfilled
|
|
27
|
+
? AsyncLocalStorage.bind(onFulfilled)
|
|
28
|
+
: onFulfilled;
|
|
29
|
+
const boundRejected = onRejected
|
|
30
|
+
? AsyncLocalStorage.bind(onRejected)
|
|
31
|
+
: onRejected;
|
|
32
|
+
return original.call(
|
|
33
|
+
this,
|
|
34
|
+
boundFulfilled,
|
|
35
|
+
boundRejected
|
|
36
|
+
) as Promise<R1 | R2>;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
// Patch Promise.prototype.catch
|
|
42
|
+
patch(
|
|
43
|
+
Promise.prototype as any,
|
|
44
|
+
"catch",
|
|
45
|
+
(original: typeof Promise.prototype.catch) => {
|
|
46
|
+
return function <T = never>(
|
|
47
|
+
this: Promise<any>,
|
|
48
|
+
onRejected?: ((reason: any) => T | PromiseLike<T>) | undefined | null
|
|
49
|
+
): Promise<T> {
|
|
50
|
+
const boundRejected = onRejected
|
|
51
|
+
? AsyncLocalStorage.bind(onRejected)
|
|
52
|
+
: onRejected;
|
|
53
|
+
return original.call(this, boundRejected);
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
// Patch Promise.prototype.finally
|
|
59
|
+
patch(
|
|
60
|
+
Promise.prototype as any,
|
|
61
|
+
"finally",
|
|
62
|
+
(original: typeof Promise.prototype.finally) => {
|
|
63
|
+
return function (
|
|
64
|
+
this: Promise<any>,
|
|
65
|
+
onFinally?: (() => void) | undefined | null
|
|
66
|
+
): Promise<any> {
|
|
67
|
+
const boundFinally = onFinally
|
|
68
|
+
? AsyncLocalStorage.bind(onFinally)
|
|
69
|
+
: onFinally;
|
|
70
|
+
return original.call(this, boundFinally);
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Remove all Promise patches, restoring original behavior.
|
|
78
|
+
*/
|
|
79
|
+
export function unpatchPromise(): void {
|
|
80
|
+
unpatch(Promise.prototype as any, "then");
|
|
81
|
+
unpatch(Promise.prototype as any, "catch");
|
|
82
|
+
unpatch(Promise.prototype as any, "finally");
|
|
83
|
+
}
|