@agoric/vow 0.1.1-dev-9cdb01d.0 → 0.1.1-dev-6ee57ba.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/README.md +62 -1
- package/package.json +4 -4
- package/src/tools.d.ts +9 -1
- package/src/tools.d.ts.map +1 -1
- package/src/tools.js +26 -3
- package/src/types.d.ts +3 -0
- package/src/types.d.ts.map +1 -1
- package/src/types.js +1 -0
- package/src/watch-utils.d.ts +11 -5
- package/src/watch-utils.d.ts.map +1 -1
- package/src/watch-utils.js +100 -40
package/README.md
CHANGED
|
@@ -27,7 +27,7 @@ Here they are: {
|
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
You can use `heapVowE` exported from `@agoric/vow`, which converts a chain of
|
|
30
|
-
promises and vows to a promise for its final
|
|
30
|
+
promises and vows to a promise for its final fulfillment, by unwrapping any
|
|
31
31
|
intermediate vows:
|
|
32
32
|
|
|
33
33
|
```js
|
|
@@ -77,6 +77,67 @@ const { watch, makeVowKit } = prepareVowTools(vowZone);
|
|
|
77
77
|
// Vows and resolvers you create can be saved in durable stores.
|
|
78
78
|
```
|
|
79
79
|
|
|
80
|
+
## VowTools
|
|
81
|
+
|
|
82
|
+
VowTools are a set of utility functions for working with Vows in Agoric smart contracts and vats. These tools help manage asynchronous operations in a way that's resilient to vat upgrades, ensuring your smart contract can handle long-running processes reliably.
|
|
83
|
+
|
|
84
|
+
### Usage
|
|
85
|
+
|
|
86
|
+
VowTools are typically prepared in the start function of a smart contract or vat and passed in as a power to exos.
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
```javascript
|
|
90
|
+
import { prepareVowTools } from '@agoric/vow/vat.js';
|
|
91
|
+
import { makeDurableZone } from '@agoric/zone/durable.js';
|
|
92
|
+
|
|
93
|
+
export const start = async (zcf, privateArgs, baggage) => {
|
|
94
|
+
const zone = makeDurableZone(baggage);
|
|
95
|
+
const vowTools = prepareVowTools(zone.subZone('vows'));
|
|
96
|
+
|
|
97
|
+
// Use vowTools here...
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Available Tools
|
|
102
|
+
|
|
103
|
+
#### `when(vowOrPromise)`
|
|
104
|
+
Returns a Promise for the fulfillment of the very end of the `vowOrPromise` chain. It can retry disconnections due to upgrades of other vats, but cannot survive the upgrade of the calling vat.
|
|
105
|
+
|
|
106
|
+
#### `watch(promiseOrVow, [watcher], [context])`
|
|
107
|
+
Watch a Vow and optionally provide a `watcher` with `onFulfilled`/`onRejected` handlers and a `context` value for the handlers. When handlers are not provided the fulfillment or rejection will simply pass through.
|
|
108
|
+
|
|
109
|
+
It also registers pending Promises, so if the current vat is upgraded, the watcher is rejected because the Promise was lost when the heap was reset.
|
|
110
|
+
|
|
111
|
+
#### `all(arrayOfPassables, [watcher], [context])`
|
|
112
|
+
Vow-tolerant implementation of Promise.all that takes an iterable of vows and other Passables and returns a single Vow. It resolves with an array of values when all of the input's promises or vows are fulfilled and rejects with the first rejection reason when any of the input's promises or vows are rejected.
|
|
113
|
+
|
|
114
|
+
#### `allSettled(arrayOfPassables, [watcher], [context])`
|
|
115
|
+
Vow-tolerant implementation of Promise.allSettled that takes an iterable of vows and other Passables and returns a single Vow. It resolves when all of the input's promises or vows are settled with an array of settled outcome objects.
|
|
116
|
+
|
|
117
|
+
#### `asVow(fn)`
|
|
118
|
+
Takes a function that might return synchronously, throw an Error, or return a Promise or Vow and returns a Vow.
|
|
119
|
+
|
|
120
|
+
#### `asPromise(vow)`
|
|
121
|
+
Converts a Vow back into a Promise.
|
|
122
|
+
|
|
123
|
+
### Example
|
|
124
|
+
|
|
125
|
+
```javascript
|
|
126
|
+
const { when, watch, all, allSettled } = vowTools;
|
|
127
|
+
|
|
128
|
+
// Using watch to create a Vow
|
|
129
|
+
const myVow = watch(someAsyncOperation());
|
|
130
|
+
|
|
131
|
+
// Using when to resolve a Vow
|
|
132
|
+
const result = await when(myVow);
|
|
133
|
+
|
|
134
|
+
// Using all
|
|
135
|
+
const results = await when(all([vow, vowForVow, promise]));
|
|
136
|
+
|
|
137
|
+
// Using allSettled
|
|
138
|
+
const outcomes = await when(allSettled([vow, vowForVow, promise]));
|
|
139
|
+
```
|
|
140
|
+
|
|
80
141
|
## Internals
|
|
81
142
|
|
|
82
143
|
The current "version 0" vow internals expose a `shorten()` method, returning a
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agoric/vow",
|
|
3
|
-
"version": "0.1.1-dev-
|
|
3
|
+
"version": "0.1.1-dev-6ee57ba.0+6ee57ba",
|
|
4
4
|
"description": "Remote (shortening and disconnection-tolerant) Promise-likes",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
"lint:types": "tsc"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@agoric/base-zone": "0.1.1-dev-
|
|
23
|
-
"@agoric/internal": "0.3.3-dev-
|
|
22
|
+
"@agoric/base-zone": "0.1.1-dev-6ee57ba.0+6ee57ba",
|
|
23
|
+
"@agoric/internal": "0.3.3-dev-6ee57ba.0+6ee57ba",
|
|
24
24
|
"@endo/env-options": "^1.1.6",
|
|
25
25
|
"@endo/errors": "^1.2.5",
|
|
26
26
|
"@endo/eventual-send": "^1.2.5",
|
|
@@ -55,5 +55,5 @@
|
|
|
55
55
|
"typeCoverage": {
|
|
56
56
|
"atLeast": 89.96
|
|
57
57
|
},
|
|
58
|
-
"gitHead": "
|
|
58
|
+
"gitHead": "6ee57ba11bfa5f9202e8d4405c2e57ebfe3667ae"
|
|
59
59
|
}
|
package/src/tools.d.ts
CHANGED
|
@@ -4,7 +4,15 @@ export function prepareBasicVowTools(zone: Zone, powers?: {
|
|
|
4
4
|
when: <T, TResult1 = import("./types.js").EUnwrap<T>, TResult2 = never>(specimenP: T, onFulfilled?: ((value: import("./types.js").EUnwrap<T>) => TResult1 | PromiseLike<TResult1>) | undefined, onRejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined) => Promise<TResult1 | TResult2>;
|
|
5
5
|
watch: <T = any, TResult1 = T, TResult2 = never, C extends any[] = any[]>(specimenP: EVow<T>, watcher?: import("./types.js").Watcher<T, TResult1, TResult2, C> | undefined, ...watcherArgs: C) => Vow<Exclude<TResult1, void> | Exclude<TResult2, void> extends never ? TResult1 : Exclude<TResult1, void> | Exclude<TResult2, void>>;
|
|
6
6
|
makeVowKit: <T>() => import("./types.js").VowKit<T>;
|
|
7
|
-
|
|
7
|
+
all: (maybeVows: unknown[]) => Vow<any[]>;
|
|
8
|
+
allVows: (maybeVows: unknown[]) => Vow<any[]>;
|
|
9
|
+
allSettled: (maybeVows: unknown[]) => Vow<({
|
|
10
|
+
status: "fulfilled";
|
|
11
|
+
value: any;
|
|
12
|
+
} | {
|
|
13
|
+
status: "rejected";
|
|
14
|
+
reason: any;
|
|
15
|
+
})[]>;
|
|
8
16
|
asVow: <T extends unknown>(fn: (...args: any[]) => Vow<Awaited<T>> | Awaited<T> | import("./types.js").PromiseVow<T>) => Vow<Awaited<T>>;
|
|
9
17
|
asPromise: AsPromiseFunction;
|
|
10
18
|
retriable: <F extends (...args: any[]) => Promise<any>>(fnZone: Zone, name: string, fn: F) => F extends (...args: infer Args) => Promise<infer R> ? (...args: Args) => Vow<R> : never;
|
package/src/tools.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["tools.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["tools.js"],"names":[],"mappings":"AAqBO,2CAJI,IAAI;;;;;;qBA4CF,OAAO,EAAE;yBAAT,OAAO,EAAE;4BAgBT,OAAO,EAAE;;;;;;;oCAFc,GACpC;;gBAnCkD,CAAC,SAApC,CAAE,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,GAAG,CAAE,UACpC,IAAI,QACJ,MAAM,MACN,CAAC,KACC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,MAAM,IAAI,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC,CAAC,GAAG,KAAK;EAmDrG;uBAGa,UAAU,CAAC,OAAO,oBAAoB,CAAC;0BA3F9B,mBAAmB;uCAE8B,YAAY;0BAAZ,YAAY;yBAAZ,YAAY;uCAAZ,YAAY"}
|
package/src/tools.js
CHANGED
|
@@ -7,6 +7,7 @@ import { makeWhen } from './when.js';
|
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* @import {Zone} from '@agoric/base-zone';
|
|
10
|
+
* @import {Passable} from '@endo/pass-style';
|
|
10
11
|
* @import {IsRetryableReason, AsPromiseFunction, EVow, Vow, ERef} from './types.js';
|
|
11
12
|
*/
|
|
12
13
|
|
|
@@ -52,11 +53,31 @@ export const prepareBasicVowTools = (zone, powers = {}) => {
|
|
|
52
53
|
};
|
|
53
54
|
|
|
54
55
|
/**
|
|
55
|
-
* Vow-tolerant implementation of Promise.all
|
|
56
|
+
* Vow-tolerant implementation of Promise.all that takes an iterable of vows
|
|
57
|
+
* and other {@link Passable}s and returns a single {@link Vow}. It resolves
|
|
58
|
+
* with an array of values when all of the input's promises or vows are
|
|
59
|
+
* fulfilled and rejects when any of the input's promises or vows are
|
|
60
|
+
* rejected with the first rejection reason.
|
|
56
61
|
*
|
|
57
|
-
* @param {
|
|
62
|
+
* @param {unknown[]} maybeVows
|
|
58
63
|
*/
|
|
59
|
-
const
|
|
64
|
+
const all = maybeVows => watchUtils.all(maybeVows);
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @param {unknown[]} maybeVows
|
|
68
|
+
* @deprecated use `vowTools.all`
|
|
69
|
+
*/
|
|
70
|
+
const allVows = all;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Vow-tolerant implementation of Promise.allSettled that takes an iterable
|
|
74
|
+
* of vows and other {@link Passable}s and returns a single {@link Vow}. It
|
|
75
|
+
* resolves when all of the input's promises or vows are settled with an
|
|
76
|
+
* array of settled outcome objects.
|
|
77
|
+
*
|
|
78
|
+
* @param {unknown[]} maybeVows
|
|
79
|
+
*/
|
|
80
|
+
const allSettled = maybeVows => watchUtils.allSettled(maybeVows);
|
|
60
81
|
|
|
61
82
|
/** @type {AsPromiseFunction} */
|
|
62
83
|
const asPromise = (specimenP, ...watcherArgs) =>
|
|
@@ -66,7 +87,9 @@ export const prepareBasicVowTools = (zone, powers = {}) => {
|
|
|
66
87
|
when,
|
|
67
88
|
watch,
|
|
68
89
|
makeVowKit,
|
|
90
|
+
all,
|
|
69
91
|
allVows,
|
|
92
|
+
allSettled,
|
|
70
93
|
asVow,
|
|
71
94
|
asPromise,
|
|
72
95
|
retriable,
|
package/src/types.d.ts
CHANGED
|
@@ -35,6 +35,9 @@ export type VowV0<T = any> = {
|
|
|
35
35
|
export type VowPayload<T = any> = {
|
|
36
36
|
vowV0: RemotableObject & Remote<VowV0<T>>;
|
|
37
37
|
};
|
|
38
|
+
/**
|
|
39
|
+
* Vows are objects that represent promises that can be stored durably.
|
|
40
|
+
*/
|
|
38
41
|
export type Vow<T = any> = CopyTagged<"Vow", VowPayload<T>>;
|
|
39
42
|
export type VowKit<T = any> = {
|
|
40
43
|
vow: Vow<T>;
|
package/src/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["types.js"],"names":[],"mappings":";;;yCAaW,GAAG,mBACH,GAAG,KAED,GAAG;;;;;uBAKH,CAAC,IACD,OAAO,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;iBAKnB,CAAC,IACD,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC;;;;iBAKlB,CAAC,IACD,IAAI,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;;;;;oBAMhB,CAAC,IACD,CACR,CAAC,SAAS,GAAG,CAAC,MAAM,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GACnC,CAAC,SAAS,WAAW,CAAC,MAAM,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GAC3C,CAAC,CACF;;;;;;;kBAIU,CAAC;;;;;;;aAMD,MAAM,OAAO,CAAC,CAAC,CAAC;;uBAOhB,CAAC;WAED,eAAe,GAAG,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["types.js"],"names":[],"mappings":";;;yCAaW,GAAG,mBACH,GAAG,KAED,GAAG;;;;;uBAKH,CAAC,IACD,OAAO,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;iBAKnB,CAAC,IACD,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC;;;;iBAKlB,CAAC,IACD,IAAI,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;;;;;oBAMhB,CAAC,IACD,CACR,CAAC,SAAS,GAAG,CAAC,MAAM,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GACnC,CAAC,SAAS,WAAW,CAAC,MAAM,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GAC3C,CAAC,CACF;;;;;;;kBAIU,CAAC;;;;;;;aAMD,MAAM,OAAO,CAAC,CAAC,CAAC;;uBAOhB,CAAC;WAED,eAAe,GAAG,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC;;;;;gBAKlC,CAAC,UACF,WAAW,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC;mBAI/B,CAAC,UACF;IACR,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;IACZ,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC;CAC1B;wBAIU,CAAC,UACF;IAAE,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAAC,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,GAAG,IAAI,CAAA;CAAE;oBAIvE,CAAC,QACD,QAAQ,MACR,QAAQ,UACA,CAAC,SAAT,GAAG,EAAG;2BAEE,CAAC,WAAW,CAAC,KAAK,GAAG,CAAC,QAAQ,CAAC,GAAG,UAAU,CAAC,QAAQ,CAAC,GAAG,QAAQ;2BAChE,GAAG,WAAW,CAAC,KAAK,GAAG,CAAC,QAAQ,CAAC,GAAG,UAAU,CAAC,QAAQ,CAAC,GAAG,QAAQ;;;;;8BAM5E,CAAC,QACD,QAAQ,MACR,QAAQ,UACA,CAAC,SAAT,GAAG,EAAG,wBAET,IAAI,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,2FAGd,OAAO,CAAC,QAAQ,GAAG,QAAQ,CAAC;qCAtGP,kBAAkB;4BAC3B,kBAAkB;gCAFd,kBAAkB"}
|
package/src/types.js
CHANGED
package/src/watch-utils.d.ts
CHANGED
|
@@ -4,10 +4,16 @@ export function prepareWatchUtils(zone: Zone, { watch, when, makeVowKit, isRetry
|
|
|
4
4
|
makeVowKit: () => VowKit<any>;
|
|
5
5
|
isRetryableReason: IsRetryableReason;
|
|
6
6
|
}): () => import("@endo/exo").Guarded<{
|
|
7
|
-
/**
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
/** @param {unknown[]} specimens */
|
|
8
|
+
all(specimens: unknown[]): Vow<any[]>;
|
|
9
|
+
/** @param {unknown[]} specimens */
|
|
10
|
+
allSettled(specimens: unknown[]): Vow<({
|
|
11
|
+
status: "fulfilled";
|
|
12
|
+
value: any;
|
|
13
|
+
} | {
|
|
14
|
+
status: "rejected";
|
|
15
|
+
reason: any;
|
|
16
|
+
})[]>;
|
|
11
17
|
/** @type {AsPromiseFunction} */
|
|
12
18
|
asPromise(specimenP: any, watcher: import("./types.js").Watcher<any, any, never, any[]> | undefined, watcherArgs: any[] | undefined): Promise<any>;
|
|
13
19
|
}>;
|
|
@@ -16,5 +22,5 @@ import type { Watch } from './watch.js';
|
|
|
16
22
|
import type { When } from './when.js';
|
|
17
23
|
import type { VowKit } from './types.js';
|
|
18
24
|
import type { IsRetryableReason } from './types.js';
|
|
19
|
-
import type {
|
|
25
|
+
import type { Vow } from './types.js';
|
|
20
26
|
//# sourceMappingURL=watch-utils.d.ts.map
|
package/src/watch-utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"watch-utils.d.ts","sourceRoot":"","sources":["watch-utils.js"],"names":[],"mappings":"AA4CO,wCAPI,IAAI,kDAEZ;IAAsB,KAAK,EAAnB,KAAK;IACQ,IAAI,EAAjB,IAAI;IACsB,UAAU,EAApC,MAAM,OAAO,GAAG,CAAC;IACS,iBAAiB,EAA3C,iBAAiB;CAC3B;
|
|
1
|
+
{"version":3,"file":"watch-utils.d.ts","sourceRoot":"","sources":["watch-utils.js"],"names":[],"mappings":"AA4CO,wCAPI,IAAI,kDAEZ;IAAsB,KAAK,EAAnB,KAAK;IACQ,IAAI,EAAjB,IAAI;IACsB,UAAU,EAApC,MAAM,OAAO,GAAG,CAAC;IACS,iBAAiB,EAA3C,iBAAiB;CAC3B;IA4CO,mCAAmC;mBAAvB,OAAO,EAAE;IAIrB,mCAAmC;0BAAvB,OAAO,EAAE,GAED,IAAI,CAAC;QAAC,MAAM,EAAE,WAAW,CAAC;QAAC,KAAK,EAAE,GAAG,CAAA;KAAC,GAAG;QAAC,MAAM,EAAE,UAAU,CAAC;QAAC,MAAM,EAAE,GAAG,CAAA;KAAC,CAAC,EAAE,CAAC;IAIlG,gCAAgC;;GAoLvC;0BA5QsB,mBAAmB;2BAClB,YAAY;0BACb,WAAW;4BACkC,YAAY;uCAAZ,YAAY;yBAAZ,YAAY"}
|
package/src/watch-utils.js
CHANGED
|
@@ -10,7 +10,7 @@ const { Fail, bare, details: X } = assert;
|
|
|
10
10
|
* @import {Zone} from '@agoric/base-zone';
|
|
11
11
|
* @import {Watch} from './watch.js';
|
|
12
12
|
* @import {When} from './when.js';
|
|
13
|
-
* @import {VowKit, AsPromiseFunction, IsRetryableReason,
|
|
13
|
+
* @import {VowKit, AsPromiseFunction, IsRetryableReason, Vow} from './types.js';
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
const VowShape = M.tagged(
|
|
@@ -54,11 +54,16 @@ export const prepareWatchUtils = (
|
|
|
54
54
|
{
|
|
55
55
|
utils: M.interface('Utils', {
|
|
56
56
|
all: M.call(M.arrayOf(M.any())).returns(VowShape),
|
|
57
|
+
allSettled: M.call(M.arrayOf(M.any())).returns(VowShape),
|
|
57
58
|
asPromise: M.call(M.raw()).rest(M.raw()).returns(M.promise()),
|
|
58
59
|
}),
|
|
59
60
|
watcher: M.interface('Watcher', {
|
|
60
|
-
onFulfilled: M.call(M.
|
|
61
|
-
onRejected: M.call(M.
|
|
61
|
+
onFulfilled: M.call(M.raw()).rest(M.raw()).returns(M.raw()),
|
|
62
|
+
onRejected: M.call(M.raw()).rest(M.raw()).returns(M.raw()),
|
|
63
|
+
}),
|
|
64
|
+
helper: M.interface('Helper', {
|
|
65
|
+
createVow: M.call(M.arrayOf(M.any()), M.boolean()).returns(VowShape),
|
|
66
|
+
processResult: M.call(M.raw()).rest(M.raw()).returns(M.undefined()),
|
|
62
67
|
}),
|
|
63
68
|
retryRejectionPromiseWatcher: PromiseWatcherI,
|
|
64
69
|
},
|
|
@@ -68,6 +73,7 @@ export const prepareWatchUtils = (
|
|
|
68
73
|
* @property {number} remaining
|
|
69
74
|
* @property {MapStore<number, any>} resultsMap
|
|
70
75
|
* @property {VowKit['resolver']} resolver
|
|
76
|
+
* @property {boolean} [isAllSettled]
|
|
71
77
|
*/
|
|
72
78
|
/** @type {MapStore<bigint, VowState>} */
|
|
73
79
|
const idToVowState = detached.mapStore('idToVowState');
|
|
@@ -79,32 +85,83 @@ export const prepareWatchUtils = (
|
|
|
79
85
|
},
|
|
80
86
|
{
|
|
81
87
|
utils: {
|
|
88
|
+
/** @param {unknown[]} specimens */
|
|
89
|
+
all(specimens) {
|
|
90
|
+
return this.facets.helper.createVow(specimens, false);
|
|
91
|
+
},
|
|
92
|
+
/** @param {unknown[]} specimens */
|
|
93
|
+
allSettled(specimens) {
|
|
94
|
+
return /** @type {Vow<({status: 'fulfilled', value: any} | {status: 'rejected', reason: any})[]>} */ (
|
|
95
|
+
this.facets.helper.createVow(specimens, true)
|
|
96
|
+
);
|
|
97
|
+
},
|
|
98
|
+
/** @type {AsPromiseFunction} */
|
|
99
|
+
asPromise(specimenP, ...watcherArgs) {
|
|
100
|
+
// Watch the specimen in case it is an ephemeral promise.
|
|
101
|
+
const vow = watch(specimenP, ...watcherArgs);
|
|
102
|
+
const promise = when(vow);
|
|
103
|
+
// Watch the ephemeral result promise to ensure that if its settlement is
|
|
104
|
+
// lost due to upgrade of this incarnation, we will at least cause an
|
|
105
|
+
// unhandled rejection in the new incarnation.
|
|
106
|
+
zone.watchPromise(promise, this.facets.retryRejectionPromiseWatcher);
|
|
107
|
+
|
|
108
|
+
return promise;
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
watcher: {
|
|
82
112
|
/**
|
|
83
|
-
* @param {
|
|
113
|
+
* @param {unknown} value
|
|
114
|
+
* @param {object} ctx
|
|
115
|
+
* @param {bigint} ctx.id
|
|
116
|
+
* @param {number} ctx.index
|
|
117
|
+
* @param {number} ctx.numResults
|
|
118
|
+
* @param {boolean} ctx.isAllSettled
|
|
84
119
|
*/
|
|
85
|
-
|
|
120
|
+
onFulfilled(value, ctx) {
|
|
121
|
+
this.facets.helper.processResult(value, ctx, 'fulfilled');
|
|
122
|
+
},
|
|
123
|
+
/**
|
|
124
|
+
* @param {unknown} reason
|
|
125
|
+
* @param {object} ctx
|
|
126
|
+
* @param {bigint} ctx.id
|
|
127
|
+
* @param {number} ctx.index
|
|
128
|
+
* @param {number} ctx.numResults
|
|
129
|
+
* @param {boolean} ctx.isAllSettled
|
|
130
|
+
*/
|
|
131
|
+
onRejected(reason, ctx) {
|
|
132
|
+
this.facets.helper.processResult(reason, ctx, 'rejected');
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
helper: {
|
|
136
|
+
/**
|
|
137
|
+
* @param {unknown[]} specimens
|
|
138
|
+
* @param {boolean} isAllSettled
|
|
139
|
+
*/
|
|
140
|
+
createVow(specimens, isAllSettled) {
|
|
86
141
|
const { nextId: id, idToVowState } = this.state;
|
|
87
142
|
/** @type {VowKit<any[]>} */
|
|
88
143
|
const kit = makeVowKit();
|
|
89
144
|
|
|
90
|
-
// Preserve the order of the
|
|
91
|
-
for (let index = 0; index <
|
|
92
|
-
watch(
|
|
145
|
+
// Preserve the order of the results.
|
|
146
|
+
for (let index = 0; index < specimens.length; index += 1) {
|
|
147
|
+
watch(specimens[index], this.facets.watcher, {
|
|
93
148
|
id,
|
|
94
149
|
index,
|
|
95
|
-
numResults:
|
|
150
|
+
numResults: specimens.length,
|
|
151
|
+
isAllSettled,
|
|
96
152
|
});
|
|
97
153
|
}
|
|
98
154
|
|
|
99
|
-
if (
|
|
155
|
+
if (specimens.length > 0) {
|
|
100
156
|
// Save the state until rejection or all fulfilled.
|
|
101
157
|
this.state.nextId += 1n;
|
|
102
158
|
idToVowState.init(
|
|
103
159
|
id,
|
|
104
160
|
harden({
|
|
105
161
|
resolver: kit.resolver,
|
|
106
|
-
remaining:
|
|
162
|
+
remaining: specimens.length,
|
|
107
163
|
resultsMap: detached.mapStore('resultsMap'),
|
|
164
|
+
isAllSettled,
|
|
108
165
|
}),
|
|
109
166
|
);
|
|
110
167
|
const idToNonStorableResults = provideLazyMap(
|
|
@@ -119,27 +176,36 @@ export const prepareWatchUtils = (
|
|
|
119
176
|
}
|
|
120
177
|
return kit.vow;
|
|
121
178
|
},
|
|
122
|
-
/**
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
return promise;
|
|
133
|
-
},
|
|
134
|
-
},
|
|
135
|
-
watcher: {
|
|
136
|
-
onFulfilled(value, { id, index, numResults }) {
|
|
179
|
+
/**
|
|
180
|
+
* @param {unknown} result
|
|
181
|
+
* @param {object} ctx
|
|
182
|
+
* @param {bigint} ctx.id
|
|
183
|
+
* @param {number} ctx.index
|
|
184
|
+
* @param {number} ctx.numResults
|
|
185
|
+
* @param {boolean} ctx.isAllSettled
|
|
186
|
+
* @param {'fulfilled' | 'rejected'} status
|
|
187
|
+
*/
|
|
188
|
+
processResult(result, { id, index, numResults, isAllSettled }, status) {
|
|
137
189
|
const { idToVowState } = this.state;
|
|
138
190
|
if (!idToVowState.has(id)) {
|
|
139
191
|
// Resolution of the returned vow happened already.
|
|
140
192
|
return;
|
|
141
193
|
}
|
|
142
194
|
const { remaining, resultsMap, resolver } = idToVowState.get(id);
|
|
195
|
+
if (!isAllSettled && status === 'rejected') {
|
|
196
|
+
// For 'all', we reject immediately on the first rejection
|
|
197
|
+
idToVowState.delete(id);
|
|
198
|
+
resolver.reject(result);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const possiblyWrappedResult = isAllSettled
|
|
203
|
+
? harden({
|
|
204
|
+
status,
|
|
205
|
+
[status === 'fulfilled' ? 'value' : 'reason']: result,
|
|
206
|
+
})
|
|
207
|
+
: result;
|
|
208
|
+
|
|
143
209
|
const idToNonStorableResults = provideLazyMap(
|
|
144
210
|
utilsToNonStorableResults,
|
|
145
211
|
this.facets.utils,
|
|
@@ -152,15 +218,16 @@ export const prepareWatchUtils = (
|
|
|
152
218
|
);
|
|
153
219
|
|
|
154
220
|
// Capture the fulfilled value.
|
|
155
|
-
if (zone.isStorable(
|
|
156
|
-
resultsMap.init(index,
|
|
221
|
+
if (zone.isStorable(possiblyWrappedResult)) {
|
|
222
|
+
resultsMap.init(index, possiblyWrappedResult);
|
|
157
223
|
} else {
|
|
158
|
-
nonStorableResults.set(index,
|
|
224
|
+
nonStorableResults.set(index, possiblyWrappedResult);
|
|
159
225
|
}
|
|
160
226
|
const vowState = harden({
|
|
161
227
|
remaining: remaining - 1,
|
|
162
228
|
resultsMap,
|
|
163
229
|
resolver,
|
|
230
|
+
isAllSettled,
|
|
164
231
|
});
|
|
165
232
|
if (vowState.remaining > 0) {
|
|
166
233
|
idToVowState.set(id, vowState);
|
|
@@ -177,9 +244,12 @@ export const prepareWatchUtils = (
|
|
|
177
244
|
results[i] = resultsMap.get(i);
|
|
178
245
|
} else {
|
|
179
246
|
numLost += 1;
|
|
247
|
+
results[i] = isAllSettled
|
|
248
|
+
? { status: 'rejected', reason: 'Unstorable result was lost' }
|
|
249
|
+
: undefined;
|
|
180
250
|
}
|
|
181
251
|
}
|
|
182
|
-
if (numLost > 0) {
|
|
252
|
+
if (numLost > 0 && !isAllSettled) {
|
|
183
253
|
resolver.reject(
|
|
184
254
|
assert.error(X`${numLost} unstorable results were lost`),
|
|
185
255
|
);
|
|
@@ -187,16 +257,6 @@ export const prepareWatchUtils = (
|
|
|
187
257
|
resolver.resolve(harden(results));
|
|
188
258
|
}
|
|
189
259
|
},
|
|
190
|
-
onRejected(value, { id, index: _index, numResults: _numResults }) {
|
|
191
|
-
const { idToVowState } = this.state;
|
|
192
|
-
if (!idToVowState.has(id)) {
|
|
193
|
-
// First rejection wins.
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
|
-
const { resolver } = idToVowState.get(id);
|
|
197
|
-
idToVowState.delete(id);
|
|
198
|
-
resolver.reject(value);
|
|
199
|
-
},
|
|
200
260
|
},
|
|
201
261
|
retryRejectionPromiseWatcher: {
|
|
202
262
|
onFulfilled(_result) {},
|