Skip to content

Commit

Permalink
feat(vowTools): asPromise helper for unwrapping vows
Browse files Browse the repository at this point in the history
  • Loading branch information
0xpatrickdev committed Jul 1, 2024
1 parent bf430a1 commit c940d5c
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 3 deletions.
8 changes: 6 additions & 2 deletions packages/vow/src/tools.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { prepareWatchUtils } from './watch-utils.js';
import { makeAsVow } from './vow-utils.js';

/** @import {Zone} from '@agoric/base-zone' */
/** @import {IsRetryableReason} from './types.js' */
/** @import {IsRetryableReason, AsPromiseFunction} from './types.js' */

/**
* @param {Zone} zone
Expand Down Expand Up @@ -35,7 +35,11 @@ export const prepareVowTools = (zone, powers = {}) => {
*/
const allVows = vows => watchUtils.all(vows);

return harden({ when, watch, makeVowKit, allVows, asVow });
/** @type {AsPromiseFunction} */
const asPromise = (specimenP, ...watcherArgs) =>
watchUtils.asPromise(specimenP, ...watcherArgs);

return harden({ when, watch, makeVowKit, allVows, asVow, asPromise });
};
harden(prepareVowTools);

Expand Down
14 changes: 14 additions & 0 deletions packages/vow/src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,17 @@ export {};
* @property {(value: T, ...args: C) => Vow<TResult1> | PromiseVow<TResult1> | TResult1} [onFulfilled]
* @property {(reason: any, ...args: C) => Vow<TResult2> | PromiseVow<TResult2> | TResult2} [onRejected]
*/

/**
* Converts a vow or promise to a promise, ensuring proper handling of ephemeral promises.
*
* @template [T=any]
* @template [TResult1=T]
* @template [TResult2=never]
* @template {any[]} [C=any[]]
* @callback AsPromiseFunction
* @param {ERef<T | Vow<T>>} specimenP
* @param {Watcher<T, TResult1, TResult2, C>} [watcher]
* @param {C} [watcherArgs]
* @returns {Promise<TResult1 | TResult2>}
*/
3 changes: 2 additions & 1 deletion packages/vow/src/watch-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const { Fail, bare } = assert;
* @import { Zone } from '@agoric/base-zone'
* @import { Watch } from './watch.js'
* @import { When } from './when.js'
* @import {VowKit} from './types.js'
* @import {VowKit, AsPromiseFunction} from './types.js'
* @import {IsRetryableReason} from './types.js'
*/

Expand Down Expand Up @@ -96,6 +96,7 @@ export const prepareWatchUtils = (
}
return kit.vow;
},
/** @type {AsPromiseFunction} */
asPromise(specimenP, ...watcherArgs) {
// Watch the specimen in case it is an ephemeral promise.
const vow = watch(specimenP, ...watcherArgs);
Expand Down
52 changes: 52 additions & 0 deletions packages/vow/test/watch-utils.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -199,3 +199,55 @@ test('allVows does NOT support Vow pipelining', async t => {
message: 'target has no method "getAddress", has []',
});
});

test('asPromise converts a vow to a promise', async t => {
const zone = makeHeapZone();
const { watch, asPromise } = prepareVowTools(zone);

const testPromiseP = Promise.resolve('test value');
const vow = watch(testPromiseP);

const result = await asPromise(vow);
t.is(result, 'test value');
});

test('asPromise handles vow rejection', async t => {
const zone = makeHeapZone();
const { watch, asPromise } = prepareVowTools(zone);

const testPromiseP = Promise.reject(new Error('test error'));
const vow = watch(testPromiseP);

await t.throwsAsync(asPromise(vow), { message: 'test error' });
});

test('asPromise accepts and resolves promises', async t => {
const zone = makeHeapZone();
const { asPromise } = prepareVowTools(zone);

const p = Promise.resolve('a promise');
const result = await asPromise(p);
t.is(result, 'a promise');
});

test('asPromise handles watcher arguments', async t => {
const zone = makeHeapZone();
const { watch, asPromise } = prepareVowTools(zone);

const testPromiseP = Promise.resolve('watcher test');
const vow = watch(testPromiseP);

let watcherCalled = false;
const watcher = {
onFulfilled(value, ctx) {
watcherCalled = true;
t.is(value, 'watcher test');
t.deepEqual(ctx, ['ctx']);
return value;
},
};

const result = await asPromise(vow, watcher, ['ctx']);
t.is(result, 'watcher test');
t.true(watcherCalled);
});

0 comments on commit c940d5c

Please sign in to comment.